test_sync_api.c (24926B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 3, or 8 (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/test_sync_api.c 21 * @brief testcase to test sync's HTTP API interface 22 * @author Christian Grothoff 23 */ 24 #include "platform.h" 25 #include <taler/taler_util.h> 26 #include <taler/taler_signatures.h> 27 #include <taler/taler_exchange_service.h> 28 #include <taler/taler_json_lib.h> 29 #include <gnunet/gnunet_util_lib.h> 30 #include <microhttpd.h> 31 #include <taler/taler_bank_service.h> 32 #include <taler/taler_fakebank_lib.h> 33 #include <taler/taler_testing_lib.h> 34 #include <taler/taler_merchant_testing_lib.h> 35 #include <taler/taler_error_codes.h> 36 #include "sync/sync_service.h" 37 #include "sync/sync_testing_lib.h" 38 #include "sync_util.h" 39 40 41 /** 42 * Configuration file we use. One (big) configuration is used 43 * for the various components for this test. 44 */ 45 #define CONFIG_FILE "test_sync_api.conf" 46 47 /** 48 * Account number of some user. 49 */ 50 #define USER_ACCOUNT_NAME "62" 51 52 /** 53 * Account number used by the merchant 54 */ 55 #define MERCHANT_ACCOUNT_NAME "3" 56 57 /** 58 * Payto URI of the customer (payer). 59 */ 60 static struct TALER_FullPayto payer_payto; 61 62 /** 63 * Payto URI of the merchant (receiver). 64 */ 65 static struct TALER_FullPayto merchant_payto; 66 67 /** 68 * Configuration of the bank. 69 */ 70 static struct TALER_TESTING_Credentials cred; 71 72 /** 73 * Merchant base URL. 74 */ 75 static const char *merchant_url = "http://localhost:8080/"; 76 77 /** 78 * Sync base URL. 79 */ 80 static const char *sync_url = "http://localhost:8084/"; 81 82 83 /** 84 * Execute the taler-exchange-wirewatch command with 85 * our configuration file. 86 * 87 * @param label label to use for the command. 88 */ 89 static struct TALER_TESTING_Command 90 cmd_exec_wirewatch (const char *label) 91 { 92 return TALER_TESTING_cmd_exec_wirewatch (label, 93 CONFIG_FILE); 94 } 95 96 97 /** 98 * Run wire transfer of funds from some user's account to the 99 * exchange. 100 * 101 * @param label label to use for the command. 102 * @param amount amount to transfer, i.e. "EUR:1" 103 * @param url exchange_url 104 */ 105 static struct TALER_TESTING_Command 106 cmd_transfer_to_exchange (const char *label, 107 const char *amount) 108 { 109 return TALER_TESTING_cmd_admin_add_incoming (label, 110 amount, 111 &cred.ba, 112 payer_payto); 113 } 114 115 116 /** 117 * Main function that will tell the interpreter what commands to 118 * run. 119 * 120 * @param cls closure 121 * @param is interpreter state 122 */ 123 static void 124 run (void *cls, 125 struct TALER_TESTING_Interpreter *is) 126 { 127 /* Payload of the blocks we upload. data[0] is the initial content of 128 block A, which is later updated to data[1]; hashes[] holds the 129 hashes of the *final* content of blocks A, C and D. */ 130 static const char *const data[] = { 131 "Hello World", 132 "Block B data", 133 "Block C data", 134 "Block D data" 135 }; 136 static const char obj_data_A[] = "object-A-data"; 137 static const char obj_data_B[] = "object-B-data"; 138 /* Nonces of blocks A, C and D, in chain order. */ 139 struct SYNC_BlockNonce nonces[3]; 140 /* Hashes of blocks A, C and D, in chain order. */ 141 struct GNUNET_HashCode hashes[3]; 142 /* Nonce of a block that is never successfully uploaded. */ 143 struct SYNC_BlockNonce nonce_stale; 144 /* Nonce that never exists on the server. */ 145 struct SYNC_BlockNonce bogus_nonce; 146 /* Nonces of the two blocks used for the refcount tests. */ 147 struct SYNC_BlockNonce ref_nonce; 148 struct SYNC_BlockNonce ref_nonce2; 149 /* Nonce of a block whose append is rejected for a bad object ref. */ 150 struct SYNC_BlockNonce orphan_nonce; 151 /* Nonce of the block appended after that rejected append. */ 152 struct SYNC_BlockNonce tail_nonce; 153 /* UIDs of the two objects used for the refcount tests. */ 154 struct SYNC_ObjectUID obj_uids[2]; 155 /* UID of an object that is never uploaded. */ 156 struct SYNC_ObjectUID unknown_uid; 157 const int16_t ref_incs[2] = { 1, 1 }; 158 const int16_t ref_decs[2] = { -1, -1 }; 159 const int16_t ref_inc1[1] = { 1 }; 160 const int16_t ref_dec2[1] = { -2 }; 161 162 (void) cls; 163 for (unsigned int i = 0; i < 3; i++) 164 SYNC_block_nonce_generate (&nonces[i]); 165 SYNC_block_nonce_generate (&nonce_stale); 166 SYNC_block_nonce_generate (&bogus_nonce); 167 SYNC_block_nonce_generate (&ref_nonce); 168 SYNC_block_nonce_generate (&ref_nonce2); 169 SYNC_block_nonce_generate (&orphan_nonce); 170 SYNC_block_nonce_generate (&tail_nonce); 171 for (unsigned int i = 0; i < 2; i++) 172 GNUNET_CRYPTO_random_block (&obj_uids[i], 173 sizeof (obj_uids[i])); 174 GNUNET_CRYPTO_random_block (&unknown_uid, 175 sizeof (unknown_uid)); 176 for (unsigned int i = 0; i < 3; i++) 177 GNUNET_CRYPTO_hash (data[i + 1], 178 strlen (data[i + 1]), 179 &hashes[i]); 180 181 { 182 struct TALER_TESTING_Command commands[] = { 183 TALER_TESTING_cmd_system_start ("start-taler", 184 CONFIG_FILE, 185 "-efms", 186 "-u", "exchange-account-exchange", 187 "-r", "merchant-exchange-default", 188 NULL), 189 TALER_TESTING_cmd_get_exchange ("get-exchange", 190 cred.cfg, 191 NULL, 192 true, 193 true), 194 TALER_TESTING_cmd_merchant_post_instances ("instance-create-admin", 195 merchant_url, 196 "admin", 197 MHD_HTTP_NO_CONTENT), 198 TALER_TESTING_cmd_merchant_post_account ( 199 "instance-create-default-account", 200 merchant_url, 201 merchant_payto, 202 NULL, NULL, 203 MHD_HTTP_OK), 204 205 /** 206 * Move money to the exchange's bank account. 207 */ 208 cmd_transfer_to_exchange ("create-reserve-1", 209 "EUR:10.02"), 210 /** 211 * Make a reserve exist, according to the previous 212 * transfer. 213 */ 214 cmd_exec_wirewatch ("wirewatch-1"), 215 TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", 216 "create-reserve-1", 217 "EUR:5", 218 0, 219 MHD_HTTP_OK), 220 TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", 221 "create-reserve-1", 222 "EUR:5", 223 0, 224 MHD_HTTP_OK), 225 226 /* Upload first block; no lifetime yet → 402 Payment Required */ 227 SYNC_TESTING_cmd_block_append ("upload-block-1", 228 sync_url, 229 NULL, 230 &nonces[0], 231 MHD_HTTP_PAYMENT_REQUIRED, 232 data[0], 233 strlen (data[0]), 234 true, 235 0, 236 NULL, 237 NULL), 238 /* Claim the order from the merchant */ 239 TALER_TESTING_cmd_merchant_claim_order ("fetch-proposal", 240 merchant_url, 241 MHD_HTTP_OK, 242 "upload-block-1", 243 NULL), 244 /* Pay with one withdrawn coin */ 245 TALER_TESTING_cmd_merchant_pay_order ("pay-account", 246 merchant_url, 247 MHD_HTTP_OK, 248 "fetch-proposal", 249 "withdraw-coin-1", 250 "EUR:5", 251 "EUR:4.99", 252 "session-id"), 253 /* Retry same upload; server finds paid order → 204 */ 254 SYNC_TESTING_cmd_block_append ("upload-block-2", 255 sync_url, 256 "upload-block-1", 257 &nonces[0], 258 MHD_HTTP_NO_CONTENT, 259 data[0], 260 strlen (data[0]), 261 true, 262 0, 263 NULL, 264 NULL), 265 /* Update the existing block with different content */ 266 SYNC_TESTING_cmd_block_update ("upload-block-B", 267 sync_url, 268 "upload-block-2", 269 &nonces[0], 270 NULL, 271 MHD_HTTP_NO_CONTENT, 272 data[1], 273 strlen (data[1]), 274 0, 275 NULL, 276 NULL), 277 /* Append a new block C at the end of block B */ 278 SYNC_TESTING_cmd_block_append ("upload-block-C", 279 sync_url, 280 "upload-block-B", 281 &nonces[1], 282 MHD_HTTP_NO_CONTENT, 283 data[2], 284 strlen (data[2]), 285 false, 286 0, 287 NULL, 288 NULL), 289 /* List blocks for the account */ 290 SYNC_TESTING_cmd_block_list ("list-blocks", 291 sync_url, 292 "upload-block-2", 293 10, 294 NULL, 295 MHD_HTTP_OK, 296 2, 297 nonces, 298 hashes), 299 300 /* Append with stale prev_nonce (nonces[0]), 301 but last_block is now nonces[1] → 409 */ 302 SYNC_TESTING_cmd_block_append ("append-stale-prev", 303 sync_url, 304 "upload-block-B", 305 &nonce_stale, 306 MHD_HTTP_CONFLICT, 307 data[0], 308 strlen (data[0]), 309 false, 310 0, 311 NULL, 312 NULL), 313 /* Update with stale old_data_hash 314 (references upload-block-2 whose curr_hash is hash("Hello World"), 315 but block now has hash("Block B data")) → 409 */ 316 SYNC_TESTING_cmd_block_update ("update-conflict", 317 sync_url, 318 "upload-block-2", 319 &nonces[0], 320 NULL, 321 MHD_HTTP_CONFLICT, 322 data[2], 323 strlen (data[2]), 324 0, 325 NULL, 326 NULL), 327 /* Re-upload same data → 304 Not Modified */ 328 SYNC_TESTING_cmd_block_append ("reupload-same-data", 329 sync_url, 330 "upload-block-B", 331 &nonces[0], 332 MHD_HTTP_NOT_MODIFIED, 333 data[1], 334 strlen (data[1]), 335 true, 336 0, 337 NULL, 338 NULL), 339 /* List with non-existent start_nonce → 404 */ 340 SYNC_TESTING_cmd_block_list ("list-bogus-start", 341 sync_url, 342 "upload-block-2", 343 10, 344 &bogus_nonce, 345 MHD_HTTP_NOT_FOUND, 346 0, 347 NULL, 348 NULL), 349 /* Final list confirms error cases left state intact */ 350 SYNC_TESTING_cmd_block_list ("list-blocks-final", 351 sync_url, 352 "upload-block-2", 353 10, 354 NULL, 355 MHD_HTTP_OK, 356 2, 357 nonces, 358 hashes), 359 360 /* ========== Delete block tests ========== */ 361 362 /* Upload a third block D */ 363 SYNC_TESTING_cmd_block_append ("upload-block-D", 364 sync_url, 365 "upload-block-C", 366 &nonces[2], 367 MHD_HTTP_NO_CONTENT, 368 data[3], 369 strlen (data[3]), 370 false, 371 0, 372 NULL, 373 NULL), 374 /* List all 3 blocks */ 375 SYNC_TESTING_cmd_block_list ("list-3-blocks", 376 sync_url, 377 "upload-block-D", 378 10, 379 NULL, 380 MHD_HTTP_OK, 381 3, 382 nonces, 383 hashes), 384 /* Delete block D */ 385 SYNC_TESTING_cmd_block_delete ("delete-block-D", 386 sync_url, 387 "upload-block-D", 388 MHD_HTTP_NO_CONTENT, 389 0, 390 NULL, 391 NULL), 392 /* List: only A and B remain */ 393 SYNC_TESTING_cmd_block_list ("list-after-delete-D", 394 sync_url, 395 "upload-block-2", 396 10, 397 NULL, 398 MHD_HTTP_OK, 399 2, 400 nonces, 401 hashes), 402 /* Delete block D again → 404 (already gone) */ 403 SYNC_TESTING_cmd_block_delete ("delete-block-D-again", 404 sync_url, 405 "upload-block-D", 406 MHD_HTTP_NOT_FOUND, 407 0, 408 NULL, 409 NULL), 410 /* Final list confirms all is well */ 411 SYNC_TESTING_cmd_block_list ("list-blocks-end", 412 sync_url, 413 "upload-block-2", 414 10, 415 NULL, 416 MHD_HTTP_OK, 417 2, 418 nonces, 419 hashes), 420 421 /* ========== Object store and refcount tests ========== */ 422 423 /* Upload object A */ 424 SYNC_TESTING_cmd_object_upload ("upload-obj-A", 425 sync_url, 426 "upload-block-2", 427 &obj_uids[0], 428 MHD_HTTP_NO_CONTENT, 429 obj_data_A, 430 sizeof obj_data_A - 1), 431 /* Upload object B */ 432 SYNC_TESTING_cmd_object_upload ("upload-obj-B", 433 sync_url, 434 "upload-block-2", 435 &obj_uids[1], 436 MHD_HTTP_NO_CONTENT, 437 obj_data_B, 438 sizeof obj_data_B - 1), 439 440 /* Fetch object A and verify content and hash */ 441 SYNC_TESTING_cmd_object_fetch ("fetch-obj-A", 442 sync_url, 443 "upload-block-2", 444 &obj_uids[0], 445 MHD_HTTP_OK, 446 obj_data_A, 447 sizeof obj_data_A - 1), 448 449 /* Fetch object B and verify content and hash */ 450 SYNC_TESTING_cmd_object_fetch ("fetch-obj-B", 451 sync_url, 452 "upload-block-2", 453 &obj_uids[1], 454 MHD_HTTP_OK, 455 obj_data_B, 456 sizeof obj_data_B - 1), 457 458 /* Try uploading a duplicate UID → idempotent (204) */ 459 SYNC_TESTING_cmd_object_upload ("upload-obj-A-dup", 460 sync_url, 461 "upload-block-2", 462 &obj_uids[0], 463 MHD_HTTP_NO_CONTENT, 464 obj_data_B, 465 sizeof obj_data_B - 1), 466 467 /* Upload a block that references objects A (+1) and B (+1) */ 468 SYNC_TESTING_cmd_block_append ("upload-block-refs", 469 sync_url, 470 "upload-block-C", 471 &ref_nonce, 472 MHD_HTTP_NO_CONTENT, 473 data[0], 474 strlen (data[0]), 475 false, 476 2, 477 obj_uids, 478 ref_incs), 479 480 /* Delete the referencing block with matching refs (decrement). 481 Objects reach refcount 0 and are auto-deleted from the DB. */ 482 SYNC_TESTING_cmd_block_delete ("delete-block-refs", 483 sync_url, 484 "upload-block-refs", 485 MHD_HTTP_NO_CONTENT, 486 2, 487 obj_uids, 488 ref_decs), 489 490 /* Re-upload object A (it was deleted when refcount hit 0) */ 491 SYNC_TESTING_cmd_object_upload ("upload-obj-A-again", 492 sync_url, 493 "upload-block-2", 494 &obj_uids[0], 495 MHD_HTTP_NO_CONTENT, 496 obj_data_A, 497 sizeof obj_data_A - 1), 498 499 /* Upload another block referencing A (+1) */ 500 SYNC_TESTING_cmd_block_append ("upload-block-refs2", 501 sync_url, 502 "upload-block-C", 503 &ref_nonce2, 504 MHD_HTTP_NO_CONTENT, 505 data[0], 506 strlen (data[0]), 507 false, 508 1, 509 obj_uids, 510 ref_inc1), 511 512 /* Try to delete with refs that would underflow → 409 conflict */ 513 SYNC_TESTING_cmd_block_delete ("delete-block-refs-underflow", 514 sync_url, 515 "upload-block-refs2", 516 MHD_HTTP_CONFLICT, 517 1, 518 obj_uids, 519 ref_dec2), 520 /* The rejected delete left the block listed ... */ 521 SYNC_TESTING_cmd_block_list ("list-after-underflow", 522 sync_url, 523 "upload-block-2", 524 10, 525 NULL, 526 MHD_HTTP_OK, 527 0, 528 NULL, 529 NULL), 530 /* ... and the object it references is still fetchable */ 531 SYNC_TESTING_cmd_object_fetch ("fetch-obj-A-after-underflow", 532 sync_url, 533 "upload-block-2", 534 &obj_uids[0], 535 MHD_HTTP_OK, 536 obj_data_A, 537 sizeof obj_data_A - 1), 538 539 /* ========== Object and reference error paths ========== */ 540 541 /* Fetching an object that was never uploaded → 404 */ 542 SYNC_TESTING_cmd_object_fetch ("fetch-obj-unknown", 543 sync_url, 544 "upload-block-2", 545 &unknown_uid, 546 MHD_HTTP_NOT_FOUND, 547 NULL, 548 0), 549 550 /* Referencing an object that was never uploaded → 409 ... */ 551 SYNC_TESTING_cmd_block_append ("append-unknown-object-ref", 552 sync_url, 553 "upload-block-refs2", 554 &orphan_nonce, 555 MHD_HTTP_CONFLICT, 556 data[0], 557 strlen (data[0]), 558 false, 559 1, 560 &unknown_uid, 561 ref_inc1), 562 /* ... and must not have moved the tail, so the next append 563 onto upload-block-refs2 still succeeds */ 564 SYNC_TESTING_cmd_block_append ("append-after-rejected-ref", 565 sync_url, 566 "upload-block-refs2", 567 &tail_nonce, 568 MHD_HTTP_NO_CONTENT, 569 data[2], 570 strlen (data[2]), 571 false, 572 0, 573 NULL, 574 NULL), 575 576 TALER_TESTING_cmd_end () 577 }; 578 579 TALER_TESTING_run (is, 580 commands); 581 } 582 } 583 584 585 int 586 main (int argc, 587 char *const *argv) 588 { 589 (void) argc; 590 payer_payto.full_payto = 591 (char *) "payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME 592 "?receiver-name=user"; 593 merchant_payto.full_payto = 594 (char *) "payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME 595 "?receiver-name=merchant"; 596 return TALER_TESTING_main (argv, 597 "DEBUG", 598 CONFIG_FILE, 599 "exchange-account-exchange", 600 TALER_TESTING_BS_FAKEBANK, 601 &cred, 602 &run, 603 NULL); 604 } 605 606 607 /* end of test_sync_api.c */