taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

MerchantApiTest.kt (12545B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2020 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.merchantlib
     18 
     19 import io.ktor.http.HttpStatusCode.Companion.NotFound
     20 import io.ktor.http.HttpStatusCode.Companion.Gone
     21 import java.net.UnknownHostException
     22 import kotlinx.coroutines.ExperimentalCoroutinesApi
     23 import kotlinx.coroutines.runBlocking
     24 import kotlinx.coroutines.test.UnconfinedTestDispatcher
     25 import net.taler.common.Amount
     26 import net.taler.common.ContractProduct
     27 import net.taler.common.Order
     28 import net.taler.common.OrderProduct
     29 import net.taler.common.Timestamp
     30 import net.taler.merchantlib.MockHttpClient.giveJsonResponse
     31 import net.taler.merchantlib.MockHttpClient.httpClient
     32 import org.junit.Assert.assertEquals
     33 import org.junit.Assert.assertTrue
     34 import org.junit.Test
     35 
     36 @ExperimentalCoroutinesApi
     37 class MerchantApiTest {
     38 
     39     private val api = MerchantApi(httpClient, UnconfinedTestDispatcher())
     40     private val merchantConfig = MerchantConfig(
     41         baseUrl = "http://example.net/instances/testInstance",
     42         apiKey = "apiKeyFooBar"
     43     )
     44     private val orderId = "orderIdFoo"
     45 
     46     @Test
     47     fun testGetConfig() = runBlocking {
     48         httpClient.giveJsonResponse("https://backend.int.taler.net/config") {
     49             """
     50             {
     51               "currency": "INTKUDOS",
     52               "version": "0:0:0"
     53             }
     54             """.trimIndent()
     55         }
     56         api.getConfig("https://backend.int.taler.net").assertSuccess {
     57             assertEquals(ConfigResponse("0:0:0", "INTKUDOS"), it)
     58         }
     59     }
     60 
     61     @Test
     62     fun testPostOrder() = runBlocking {
     63         val product = ContractProduct(
     64             productId = "foo",
     65             description = "bar",
     66             price = Amount("TEST", 1, 0),
     67             quantity = 2
     68         )
     69         val contractTerms = Order(
     70             summary = "test",
     71             amount = Amount("TEST", 2, 1),
     72             fulfillmentUrl = "http://example.org",
     73             products = listOf(product)
     74         )
     75         val request = PostOrderRequest(contractTerms)
     76         val contractTermsJson = """
     77             {
     78                 "order": {
     79                     "summary": "${contractTerms.summary}",
     80                     "amount": "${contractTerms.amount.toJSONString()}",
     81                     "fulfillment_url": "${contractTerms.fulfillmentUrl}",
     82                     "products": [
     83                         {
     84                             "product_id": "${product.productId}",
     85                             "description": "${product.description}",
     86                             "price": "${product.price!!.toJSONString()}",
     87                             "prices_are_net": ${product.pricesAreNet},
     88                             "quantity": ${product.quantity}
     89                         }
     90                     ]
     91                 }
     92             }
     93         """.trimIndent()
     94         httpClient.giveJsonResponse(
     95             "http://example.net/instances/testInstance/private/orders",
     96             contractTermsJson
     97         ) {
     98             """{"order_id": "test"}"""
     99         }
    100         api.postOrder(merchantConfig, request).assertSuccess {
    101             assertEquals(PostOrderResponse("test"), it)
    102         }
    103 
    104         httpClient.giveJsonResponse(
    105             "http://example.net/instances/testInstance/private/orders",
    106             statusCode = NotFound
    107         ) {
    108             """{
    109                 "code": 2000,
    110                 "hint": "merchant instance unknown"
    111             }"""
    112         }
    113         api.postOrder(merchantConfig, request).assertFailure {
    114             assertTrue(it.contains("2000"))
    115             assertTrue(it.contains("merchant instance unknown"))
    116         }
    117 
    118         httpClient.giveJsonResponse(
    119             "http://example.net/instances/testInstance/private/orders",
    120             statusCode = Gone,
    121         ) {
    122             """
    123             {
    124               "product_id": "finite_quantity",
    125               "requested_quantity": 10,
    126               "unit_requested_quantity": "10",
    127               "available_quantity": 8,
    128               "unit_available_quantity": "8"
    129             }
    130             """.trimIndent()
    131         }
    132         api.postOrder(merchantConfig, request).assertFailure {
    133             assertEquals(
    134                 "Inventory stock unavailable for product finite_quantity: " +
    135                     "10 requested, 8 available.",
    136                 it,
    137             )
    138         }
    139     }
    140 
    141     @Test
    142     fun testCheckOrder() = runBlocking {
    143         val unpaidResponse = CheckPaymentResponse.Unpaid(false, "http://taler.net/foo")
    144         httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId") {
    145             """{
    146                 "order_status": "unpaid",
    147                 "paid": ${unpaidResponse.paid},
    148                 "taler_pay_uri": "${unpaidResponse.talerPayUri}"
    149             }""".trimIndent()
    150         }
    151         api.checkOrder(merchantConfig, orderId).assertSuccess {
    152             assertEquals(unpaidResponse, it)
    153         }
    154 
    155         httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId") {
    156             """{
    157                 "order_status": "paid",
    158                 "paid": true,
    159                 "refunded": true,
    160                 "refund_pending": false,
    161                 "refund_amount": "TESTKUDOS:1.5"
    162             }""".trimIndent()
    163         }
    164         api.checkOrder(merchantConfig, orderId).assertSuccess {
    165             val paidResponse = it as CheckPaymentResponse.Paid
    166             assertEquals(true, paidResponse.refunded)
    167             assertEquals(false, paidResponse.refundPending)
    168             assertEquals(Amount("TESTKUDOS", 1, 50000000), paidResponse.refundAmount)
    169         }
    170 
    171         httpClient.giveJsonResponse(
    172             "http://example.net/instances/testInstance/private/orders/$orderId",
    173             statusCode = NotFound
    174         ) {
    175             """{
    176                 "code": 2909,
    177                 "hint": "Did not find contract terms for order in DB"
    178             }"""
    179         }
    180         api.checkOrder(merchantConfig, orderId).assertFailure {
    181             assertTrue(it.contains("2909"))
    182             assertTrue(it.contains("Did not find contract terms for order in DB"))
    183         }
    184     }
    185 
    186     @Test
    187     fun testDeleteOrder() = runBlocking {
    188         httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId") {
    189             "{}"
    190         }
    191         api.deleteOrder(merchantConfig, orderId).assertSuccess {}
    192 
    193         httpClient.giveJsonResponse(
    194             "http://example.net/instances/testInstance/private/orders/$orderId",
    195             statusCode = NotFound
    196         ) {
    197             """{
    198                 "code": 2511,
    199                 "hint": "Order unknown"
    200                 }
    201             """.trimIndent()
    202         }
    203         api.deleteOrder(merchantConfig, orderId).assertFailure {
    204             assertTrue(it.contains("2511"))
    205             assertTrue(it.contains("Order unknown"))
    206         }
    207     }
    208 
    209     @Test
    210     fun testGetOrderHistory() = runBlocking {
    211         httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders?limit=-20") {
    212             """{  "orders": [
    213                     {
    214                       "order_id": "2020.217-0281FGXCS25P2",
    215                       "row_id": 183,
    216                       "timestamp": {
    217                         "t_s": 1596542338
    218                       },
    219                       "amount": "TESTKUDOS:1",
    220                       "summary": "Chips",
    221                       "refund_amount": "TESTKUDOS:0.8",
    222                       "pending_refund_amount": "TESTKUDOS:0.3",
    223                       "refundable": true,
    224                       "paid": true
    225                     },
    226                     {
    227                       "order_id": "2020.216-01G2ZPXSP6BYT",
    228                       "row_id": 154,
    229                       "timestamp": {
    230                         "t_s": 1596468174
    231                       },
    232                       "amount": "TESTKUDOS:0.8",
    233                       "summary": "Peanuts",
    234                       "refundable": false,
    235                       "paid": false
    236                     }
    237                 ]
    238             }""".trimIndent()
    239         }
    240         api.getOrderHistory(merchantConfig).assertSuccess {
    241             assertEquals(2, it.orders.size)
    242 
    243             val order1 = it.orders[0]
    244             assertEquals(Amount("TESTKUDOS", 1, 0), order1.amount)
    245             assertEquals("2020.217-0281FGXCS25P2", order1.orderId)
    246             assertEquals(true, order1.paid)
    247             assertEquals(true, order1.refundable)
    248             assertEquals("Chips", order1.summary)
    249             assertEquals(Timestamp.fromMillis(1596542338000), order1.timestamp)
    250             assertEquals(Amount("TESTKUDOS", 0, 80000000), order1.refundAmount)
    251             assertEquals(Amount("TESTKUDOS", 0, 30000000), order1.pendingRefundAmount)
    252             assertEquals(true, order1.hasRefund)
    253             assertEquals(true, order1.hasPendingRefund)
    254 
    255             val order2 = it.orders[1]
    256             assertEquals(Amount("TESTKUDOS", 0, 80000000), order2.amount)
    257             assertEquals("2020.216-01G2ZPXSP6BYT", order2.orderId)
    258             assertEquals(false, order2.paid)
    259             assertEquals(false, order2.refundable)
    260             assertEquals("Peanuts", order2.summary)
    261             assertEquals(Timestamp.fromMillis(1596468174000), order2.timestamp)
    262             assertEquals(false, order2.hasRefund)
    263             assertEquals(false, order2.hasPendingRefund)
    264         }
    265     }
    266 
    267     @Test
    268     fun testGetOrderHistoryLegacyRefundFields() = runBlocking {
    269         httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders?limit=-20") {
    270             """{  "orders": [
    271                     {
    272                       "order_id": "legacy-refund-order",
    273                       "timestamp": {
    274                         "t_s": 1596542338
    275                       },
    276                       "amount": "TESTKUDOS:1",
    277                       "summary": "Legacy refund",
    278                       "refundable": false,
    279                       "paid": true,
    280                       "refunded_amount": "TESTKUDOS:0.8",
    281                       "refund_pending_amount": "TESTKUDOS:0.2"
    282                     }
    283                 ]
    284             }""".trimIndent()
    285         }
    286         api.getOrderHistory(merchantConfig).assertSuccess {
    287             assertEquals(1, it.orders.size)
    288             val order = it.orders.single()
    289             assertEquals(Amount("TESTKUDOS", 0, 80000000), order.refundAmount)
    290             assertEquals(Amount("TESTKUDOS", 0, 20000000), order.pendingRefundAmount)
    291             assertEquals(true, order.hasRefund)
    292             assertEquals(true, order.hasPendingRefund)
    293         }
    294     }
    295 
    296     @Test
    297     fun testGetOrderHistoryNotFoundFallbackMessage() = runBlocking {
    298         httpClient.giveJsonResponse(
    299             "http://example.net/instances/testInstance/private/orders?limit=-20",
    300             statusCode = NotFound
    301         ) {
    302             "not-json"
    303         }
    304         api.getOrderHistory(merchantConfig).assertFailure {
    305             assertEquals("Not found (404): check the merchant URL and instance path.", it)
    306         }
    307     }
    308 
    309     @Test
    310     fun testResponseNetworkFailureMessage() = runBlocking {
    311         Response.response<String> {
    312             throw UnknownHostException("backend.int.taler.net")
    313         }.assertFailure {
    314             assertEquals("Network error: check your internet connection and merchant URL.", it)
    315         }
    316     }
    317 
    318     @Test
    319     fun testGiveRefund() = runBlocking {
    320         httpClient.giveJsonResponse("http://example.net/instances/testInstance/private/orders/$orderId/refund") {
    321             """{
    322                 "taler_refund_uri": "taler://refund/foo/bar"
    323             }""".trimIndent()
    324         }
    325         val request = RefundRequest(
    326             refund = Amount("TESTKUDOS", 5, 0),
    327             reason = "Give me my money back now!!!"
    328         )
    329         api.giveRefund(merchantConfig, orderId, request).assertSuccess {
    330             assertEquals("taler://refund/foo/bar", it.talerRefundUri)
    331         }
    332     }
    333 }