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 }