test_purse_deposits.c (24513B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2026 Taler Systems SA 4 5 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 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 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file exchangedb/test_purse_deposits.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `purse_deposits` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_do_purse_deposit(), 23 * #TALER_EXCHANGEDB_get_purse_deposit(), 24 * #TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id() and 25 * #TALER_EXCHANGEDB_iterate_purse_deposits_by_purse(). 26 * 27 * `purse_deposits` references `purse_requests` and `known_coins`, which 28 * TDB_purse() and TDB_coin() create. do_purse_deposit() declines in three 29 * ways: the coin has too little left, the purse has already been decided 30 * (too late), and the same coin was already paid into the purse with a 31 * different amount (conflict). 32 */ 33 #include "test_common.h" 34 #include "exchange-database/do_expire_purse.h" 35 #include "exchange-database/do_purse_deposit.h" 36 #include "exchange-database/get_purse_deposit.h" 37 #include "exchange-database/iterate_purse_deposits_above_serial_id.h" 38 #include "exchange-database/iterate_purse_deposits_by_purse.h" 39 #include "exchange-database/rollback.h" 40 #include "exchange-database/start.h" 41 42 43 /** 44 * Denomination the checks deposit. 45 */ 46 static struct TDB_Denom denom; 47 48 49 /** 50 * Build a timestamp from a number of seconds since the epoch. 51 * 52 * @param secs seconds since the epoch 53 * @return the timestamp 54 */ 55 static struct GNUNET_TIME_Timestamp 56 ts (uint64_t secs) 57 { 58 struct GNUNET_TIME_Absolute abs = { 59 .abs_value_us = secs * 1000LLU * 1000LLU 60 }; 61 62 return GNUNET_TIME_absolute_to_timestamp (abs); 63 } 64 65 66 /** 67 * Closure for #deposit_cb() and #refund_cb(). 68 */ 69 struct DepositContext 70 { 71 /** 72 * How many rows did the callback see? 73 */ 74 unsigned int total; 75 76 /** 77 * Stop after this many rows; 0 for no limit. 78 */ 79 unsigned int stop_after; 80 81 /** 82 * Coin we are looking for, NULL to match nothing. 83 */ 84 const struct TALER_CoinSpendPublicKeyP *coin_pub; 85 86 /** 87 * How many times did we see it? 88 */ 89 unsigned int matched; 90 91 /** 92 * Amount reported for it. 93 */ 94 struct TALER_Amount amount; 95 96 /** 97 * Was a reserve reported for its purse? 98 */ 99 bool have_reserve; 100 }; 101 102 103 /** 104 * Callback for 105 * #TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id(). 106 * 107 * @param cls a `struct DepositContext *` 108 * @param rowid row of the deposit 109 * @param deposit details of the deposit 110 * @param reserve_pub reserve the purse was merged into, NULL if none 111 * @param flags flags of the purse 112 * @param auditor_balance balance of the purse 113 * @param purse_total target amount of the purse 114 * @param denom_pub denomination of the coin 115 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop 116 */ 117 static enum GNUNET_GenericReturnValue 118 deposit_cb (void *cls, 119 uint64_t rowid, 120 const struct TALER_EXCHANGEDB_PurseDeposit *deposit, 121 const struct TALER_ReservePublicKeyP *reserve_pub, 122 enum TALER_WalletAccountMergeFlags flags, 123 const struct TALER_Amount *auditor_balance, 124 const struct TALER_Amount *purse_total, 125 const struct TALER_DenominationPublicKey *denom_pub) 126 { 127 struct DepositContext *ctx = cls; 128 129 (void) rowid; 130 (void) flags; 131 (void) auditor_balance; 132 (void) purse_total; 133 (void) denom_pub; 134 ctx->total++; 135 if ( (NULL != ctx->coin_pub) && 136 (0 == GNUNET_memcmp (&deposit->coin_pub, 137 ctx->coin_pub)) ) 138 { 139 ctx->matched++; 140 ctx->amount = deposit->amount; 141 ctx->have_reserve = (NULL != reserve_pub); 142 } 143 if ( (0 != ctx->stop_after) && 144 (ctx->total >= ctx->stop_after) ) 145 return GNUNET_SYSERR; 146 return GNUNET_OK; 147 } 148 149 150 /** 151 * Callback for #TALER_EXCHANGEDB_iterate_purse_deposits_by_purse(). 152 * 153 * @param cls a `struct DepositContext *` 154 * @param rowid row of the deposit 155 * @param amount_with_fee how much was paid in 156 * @param coin_pub which coin paid 157 * @param denom_pub denomination of that coin 158 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop 159 */ 160 static enum GNUNET_GenericReturnValue 161 refund_cb (void *cls, 162 uint64_t rowid, 163 const struct TALER_Amount *amount_with_fee, 164 const struct TALER_CoinSpendPublicKeyP *coin_pub, 165 const struct TALER_DenominationPublicKey *denom_pub) 166 { 167 struct DepositContext *ctx = cls; 168 169 (void) rowid; 170 (void) denom_pub; 171 ctx->total++; 172 if ( (NULL != ctx->coin_pub) && 173 (0 == GNUNET_memcmp (coin_pub, 174 ctx->coin_pub)) ) 175 { 176 ctx->matched++; 177 ctx->amount = *amount_with_fee; 178 } 179 if ( (0 != ctx->stop_after) && 180 (ctx->total >= ctx->stop_after) ) 181 return GNUNET_SYSERR; 182 return GNUNET_OK; 183 } 184 185 186 /** 187 * Outcome of a purse deposit. 188 */ 189 struct DepositStatus 190 { 191 /** 192 * Did the coin have enough left? 193 */ 194 bool balance_ok; 195 196 /** 197 * Was the purse already decided? 198 */ 199 bool too_late; 200 201 /** 202 * Was the coin already paid into this purse differently? 203 */ 204 bool conflict; 205 }; 206 207 208 /** 209 * Pay a coin into a purse. 210 * 211 * @param pg the database context 212 * @param purse purse to credit 213 * @param coin coin to debit 214 * @param seed seed for the coin signature 215 * @param amount how much to take from the coin, e.g. "1" 216 * @param fee purse deposit fee included in @a amount, e.g. "0" 217 * @param[out] st set to the outcome 218 * @return transaction status 219 */ 220 static enum GNUNET_DB_QueryStatus 221 run_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg, 222 const struct TDB_Purse *purse, 223 const struct TALER_CoinPublicInfo *coin, 224 uint32_t seed, 225 const char *amount, 226 const char *fee, 227 struct DepositStatus *st) 228 { 229 struct TALER_CoinSpendSignatureP coin_sig; 230 struct TALER_Amount a = TDB_amount (amount); 231 struct TALER_Amount f = TDB_amount (fee); 232 struct TALER_Amount net; 233 234 TDB_fill (&coin_sig, 235 sizeof (coin_sig), 236 seed); 237 GNUNET_assert (0 <= 238 TALER_amount_subtract (&net, 239 &a, 240 &f)); 241 memset (st, 242 0, 243 sizeof (*st)); 244 return TALER_EXCHANGEDB_do_purse_deposit (pg, 245 &purse->purse_pub, 246 &coin->coin_pub, 247 &a, 248 &coin_sig, 249 &net, 250 &st->balance_ok, 251 &st->too_late, 252 &st->conflict); 253 } 254 255 256 /** 257 * Nothing is reported while the table is empty. 258 * 259 * @param pg the database context 260 * @return 0 on success 261 */ 262 static int 263 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 264 { 265 struct TALER_PurseContractPublicKeyP purse_pub; 266 struct TALER_CoinSpendPublicKeyP coin_pub; 267 struct TALER_Amount amount; 268 struct TALER_DenominationHashP h_denom_pub; 269 struct TALER_AgeCommitmentHashP phac; 270 struct TALER_CoinSpendSignatureP coin_sig; 271 struct DepositContext ctx = { 0 }; 272 char *partner_url = NULL; 273 bool no_age_commitment; 274 275 TDB_denom (pg, 276 10, 277 "5", 278 "0.1", 279 &denom); 280 TDB_FILL (purse_pub, 281 1); 282 TDB_FILL (coin_pub, 283 1); 284 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 285 TALER_EXCHANGEDB_get_purse_deposit (pg, 286 &purse_pub, 287 &coin_pub, 288 &amount, 289 &h_denom_pub, 290 &phac, 291 &no_age_commitment, 292 &coin_sig, 293 &partner_url)); 294 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 295 TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id ( 296 pg, 297 0, 298 &deposit_cb, 299 &ctx)); 300 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 301 TALER_EXCHANGEDB_iterate_purse_deposits_by_purse (pg, 302 &purse_pub, 303 &refund_cb, 304 &ctx)); 305 FAILIF (0 != ctx.total); 306 return 0; 307 } 308 309 310 /** 311 * Paying a coin into a purse debits the coin and credits the purse. 312 * 313 * @param pg the database context 314 * @return 0 on success 315 */ 316 static int 317 check_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg) 318 { 319 struct TDB_Purse purse; 320 struct TALER_CoinPublicInfo coin; 321 struct DepositStatus st; 322 struct TALER_Amount amount; 323 struct TALER_Amount expect = TDB_amount ("1"); 324 struct TALER_DenominationHashP h_denom_pub; 325 struct TALER_AgeCommitmentHashP phac; 326 struct TALER_CoinSpendSignatureP coin_sig; 327 struct TALER_CoinSpendSignatureP expect_sig; 328 char *partner_url = NULL; 329 bool no_age_commitment = false; 330 331 TDB_purse (pg, 332 10, 333 "5", 334 ts (1700000000), 335 &purse); 336 TDB_coin (pg, 337 &denom, 338 20, 339 &coin, 340 NULL); 341 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 342 run_deposit (pg, 343 &purse, 344 &coin, 345 20, 346 "1", 347 "0", 348 &st), 349 TDB_coin_free (&coin)); 350 FAILIF_C (! st.balance_ok, 351 TDB_coin_free (&coin)); 352 FAILIF_C (st.too_late, 353 TDB_coin_free (&coin)); 354 FAILIF_C (st.conflict, 355 TDB_coin_free (&coin)); 356 FAILIF_C (1 != TDB_count (pg, 357 "FROM purse_deposits"), 358 TDB_coin_free (&coin)); 359 360 /* the coin was debited... */ 361 { 362 char *hex = TDB_hex (&coin.coin_pub, 363 sizeof (coin.coin_pub)); 364 365 FAILIF_C (1 != TDB_count (pg, 366 "FROM known_coins" 367 " WHERE coin_pub=decode('%s','hex')" 368 " AND remaining=ROW(4,0)::taler_amount", 369 hex), 370 GNUNET_free (hex); TDB_coin_free (&coin)); 371 GNUNET_free (hex); 372 } 373 /* ...and the purse credited */ 374 { 375 char *hex = TDB_hex (&purse.purse_pub, 376 sizeof (purse.purse_pub)); 377 378 FAILIF_C (1 != TDB_count (pg, 379 "FROM purse_requests" 380 " WHERE purse_pub=decode('%s','hex')" 381 " AND balance=ROW(1,0)::taler_amount", 382 hex), 383 GNUNET_free (hex); TDB_coin_free (&coin)); 384 GNUNET_free (hex); 385 } 386 387 TDB_FILL (expect_sig, 388 20); 389 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 390 TALER_EXCHANGEDB_get_purse_deposit (pg, 391 &purse.purse_pub, 392 &coin.coin_pub, 393 &amount, 394 &h_denom_pub, 395 &phac, 396 &no_age_commitment, 397 &coin_sig, 398 &partner_url), 399 TDB_coin_free (&coin)); 400 FAILIF_C (0 != TALER_amount_cmp (&amount, 401 &expect), 402 GNUNET_free (partner_url); TDB_coin_free (&coin)); 403 FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub, 404 &denom.h_denom_pub), 405 GNUNET_free (partner_url); TDB_coin_free (&coin)); 406 FAILIF_C (0 != GNUNET_memcmp (&coin_sig, 407 &expect_sig), 408 GNUNET_free (partner_url); TDB_coin_free (&coin)); 409 FAILIF_C (! no_age_commitment, 410 GNUNET_free (partner_url); TDB_coin_free (&coin)); 411 /* the purse is ours, so there is no partner exchange */ 412 FAILIF_C (NULL != partner_url, 413 GNUNET_free (partner_url); TDB_coin_free (&coin)); 414 415 /* re-depositing the same coin with the same amount is idempotent */ 416 FAILIF_C (0 > 417 run_deposit (pg, 418 &purse, 419 &coin, 420 20, 421 "1", 422 "0", 423 &st), 424 TDB_coin_free (&coin)); 425 FAILIF_C (1 != TDB_count (pg, 426 "FROM purse_deposits"), 427 TDB_coin_free (&coin)); 428 429 /* ...but a second attempt with a different coin signature is a conflict: 430 the idempotency check compares the signature, not the amount */ 431 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 432 run_deposit (pg, 433 &purse, 434 &coin, 435 29, 436 "1", 437 "0", 438 &st), 439 TDB_coin_free (&coin)); 440 FAILIF_C (! st.conflict, 441 TDB_coin_free (&coin)); 442 FAILIF_C (1 != TDB_count (pg, 443 "FROM purse_deposits"), 444 TDB_coin_free (&coin)); 445 TDB_coin_free (&coin); 446 return 0; 447 } 448 449 450 /** 451 * A coin without enough left cannot be paid into a purse. 452 * 453 * @param pg the database context 454 * @return 0 on success 455 */ 456 static int 457 check_insufficient_balance (struct TALER_EXCHANGEDB_PostgresContext *pg) 458 { 459 struct TDB_Purse purse; 460 struct TALER_CoinPublicInfo coin; 461 struct DepositStatus st; 462 463 TDB_purse (pg, 464 10, 465 "5", 466 ts (1700000000), 467 &purse); 468 TDB_coin (pg, 469 &denom, 470 21, 471 &coin, 472 NULL); 473 /* The `purse_deposits` row goes in before the coin balance is checked, 474 so the caller has to roll back -- which is what the /purses/.../deposit 475 handler does. */ 476 FAILIF_C (GNUNET_OK != 477 TALER_EXCHANGEDB_start (pg, 478 "test-purse-deposit-insufficient"), 479 TDB_coin_free (&coin)); 480 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 481 run_deposit (pg, 482 &purse, 483 &coin, 484 21, 485 "6", 486 "0", 487 &st), 488 TALER_EXCHANGEDB_rollback (pg); TDB_coin_free (&coin)); 489 TALER_EXCHANGEDB_rollback (pg); 490 TDB_coin_free (&coin); 491 FAILIF (st.balance_ok); 492 FAILIF (st.conflict); 493 FAILIF (1 != TDB_count (pg, 494 "FROM purse_deposits")); 495 return 0; 496 } 497 498 499 /** 500 * Once a purse has been decided, a new deposit into it is refused as too 501 * late, whether or not the purse was merged and whether or not the 502 * deposit would complete it. An idempotent replay of a deposit made 503 * before the decision still succeeds. 504 * 505 * @param pg the database context 506 * @return 0 on success 507 */ 508 static int 509 check_too_late (struct TALER_EXCHANGEDB_PostgresContext *pg) 510 { 511 struct TDB_Purse purse; 512 struct TALER_CoinPublicInfo coin; 513 struct TALER_ReservePublicKeyP reserve_pub; 514 struct DepositStatus st; 515 struct GNUNET_TIME_Absolute expiration; 516 517 /* a purse that expired in 2020, merged into a reserve */ 518 TDB_purse (pg, 519 11, 520 "5", 521 ts (1600000000), 522 &purse); 523 TDB_reserve (pg, 524 11, 525 "0", 526 &reserve_pub); 527 { 528 char *phex = TDB_hex (&purse.purse_pub, 529 sizeof (purse.purse_pub)); 530 char *rhex = TDB_hex (&reserve_pub, 531 sizeof (reserve_pub)); 532 533 FAILIF_C (GNUNET_OK != 534 TDB_exec (pg, 535 "INSERT INTO purse_merges" 536 " (reserve_pub,purse_pub,merge_sig,merge_timestamp)" 537 " VALUES (decode('%s','hex'),decode('%s','hex')" 538 " ,decode(repeat('11',64),'hex'),1000000);", 539 rhex, 540 phex), 541 GNUNET_free (phex); GNUNET_free (rhex)); 542 GNUNET_free (phex); 543 GNUNET_free (rhex); 544 } 545 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 546 TALER_EXCHANGEDB_do_expire_purse (pg, 547 GNUNET_TIME_UNIT_ZERO_ABS, 548 GNUNET_TIME_absolute_get (), 549 &expiration)); 550 TDB_coin (pg, 551 &denom, 552 22, 553 &coin, 554 NULL); 555 /* Pay in the full target amount. As with the balance check, the 556 `purse_deposits` row is written first, so roll back. */ 557 FAILIF_C (GNUNET_OK != 558 TALER_EXCHANGEDB_start (pg, 559 "test-purse-deposit-too-late"), 560 TDB_coin_free (&coin)); 561 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 562 run_deposit (pg, 563 &purse, 564 &coin, 565 22, 566 "5", 567 "0", 568 &st), 569 TALER_EXCHANGEDB_rollback (pg); TDB_coin_free (&coin)); 570 TALER_EXCHANGEDB_rollback (pg); 571 TDB_coin_free (&coin); 572 FAILIF (! st.too_late); 573 FAILIF (st.balance_ok); 574 FAILIF (st.conflict); 575 FAILIF (1 != TDB_count (pg, 576 "FROM purse_deposits")); 577 578 /* A purse that was never merged and has been decided (refunded): 579 a deposit that would not complete it is refused as well, but a 580 replay of a deposit made before the decision is still idempotent. 581 All of this runs in one transaction that is rolled back, so the 582 later checks see the table unchanged. */ 583 { 584 struct TDB_Purse purse2; 585 struct TALER_CoinPublicInfo coin2; 586 struct TALER_CoinPublicInfo coin3; 587 char *phex; 588 589 TDB_purse (pg, 590 12, 591 "5", 592 ts (1600000000), 593 &purse2); 594 TDB_coin (pg, 595 &denom, 596 23, 597 &coin2, 598 NULL); 599 TDB_coin (pg, 600 &denom, 601 24, 602 &coin3, 603 NULL); 604 phex = TDB_hex (&purse2.purse_pub, 605 sizeof (purse2.purse_pub)); 606 FAILIF_C (GNUNET_OK != 607 TALER_EXCHANGEDB_start (pg, 608 "test-purse-deposit-decided"), 609 GNUNET_free (phex); 610 TDB_coin_free (&coin2); TDB_coin_free (&coin3)); 611 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 612 run_deposit (pg, 613 &purse2, 614 &coin2, 615 23, 616 "1", 617 "0", 618 &st), 619 TALER_EXCHANGEDB_rollback (pg); GNUNET_free (phex); 620 TDB_coin_free (&coin2); TDB_coin_free (&coin3)); 621 FAILIF_C ( (! st.balance_ok) || st.too_late || st.conflict, 622 TALER_EXCHANGEDB_rollback (pg); GNUNET_free (phex); 623 TDB_coin_free (&coin2); TDB_coin_free (&coin3)); 624 FAILIF_C (GNUNET_OK != 625 TDB_exec (pg, 626 "INSERT INTO purse_decision" 627 " (purse_pub,action_timestamp,refunded)" 628 " VALUES (decode('%s','hex'),1000000,TRUE);", 629 phex), 630 TALER_EXCHANGEDB_rollback (pg); GNUNET_free (phex); 631 TDB_coin_free (&coin2); TDB_coin_free (&coin3)); 632 GNUNET_free (phex); 633 /* replay of the deposit made before the decision */ 634 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 635 run_deposit (pg, 636 &purse2, 637 &coin2, 638 23, 639 "1", 640 "0", 641 &st), 642 TALER_EXCHANGEDB_rollback (pg); 643 TDB_coin_free (&coin2); TDB_coin_free (&coin3)); 644 FAILIF_C ( (! st.balance_ok) || st.too_late || st.conflict, 645 TALER_EXCHANGEDB_rollback (pg); 646 TDB_coin_free (&coin2); TDB_coin_free (&coin3)); 647 /* a new coin, not completing the purse */ 648 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 649 run_deposit (pg, 650 &purse2, 651 &coin3, 652 24, 653 "1", 654 "0", 655 &st), 656 TALER_EXCHANGEDB_rollback (pg); 657 TDB_coin_free (&coin2); TDB_coin_free (&coin3)); 658 TALER_EXCHANGEDB_rollback (pg); 659 TDB_coin_free (&coin2); 660 TDB_coin_free (&coin3); 661 FAILIF (! st.too_late); 662 FAILIF (st.balance_ok); 663 FAILIF (st.conflict); 664 FAILIF (1 != TDB_count (pg, 665 "FROM purse_deposits")); 666 } 667 return 0; 668 } 669 670 671 /** 672 * The iterators see the deposit; the by-purse one only for its own purse. 673 * 674 * @param pg the database context 675 * @return 0 on success 676 */ 677 static int 678 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 679 { 680 struct TALER_CoinSpendPublicKeyP coin_pub; 681 struct TALER_PurseContractPublicKeyP purse_pub; 682 struct DepositContext ctx; 683 struct TALER_Amount expect = TDB_amount ("1"); 684 685 TDB_FILL (coin_pub, 686 20); 687 TDB_FILL (purse_pub, 688 10); 689 memset (&ctx, 690 0, 691 sizeof (ctx)); 692 ctx.coin_pub = &coin_pub; 693 FAILIF (1 != 694 TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id ( 695 pg, 696 0, 697 &deposit_cb, 698 &ctx)); 699 FAILIF (1 != ctx.matched); 700 FAILIF (0 != TALER_amount_cmp (&ctx.amount, 701 &expect)); 702 /* the purse was never merged, so there is no reserve */ 703 FAILIF (ctx.have_reserve); 704 705 memset (&ctx, 706 0, 707 sizeof (ctx)); 708 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 709 TALER_EXCHANGEDB_iterate_purse_deposits_above_serial_id ( 710 pg, 711 1000, 712 &deposit_cb, 713 &ctx)); 714 FAILIF (0 != ctx.total); 715 716 memset (&ctx, 717 0, 718 sizeof (ctx)); 719 ctx.coin_pub = &coin_pub; 720 FAILIF (1 != 721 TALER_EXCHANGEDB_iterate_purse_deposits_by_purse (pg, 722 &purse_pub, 723 &refund_cb, 724 &ctx)); 725 FAILIF (1 != ctx.matched); 726 FAILIF (0 != TALER_amount_cmp (&ctx.amount, 727 &expect)); 728 729 /* a purse nobody paid into has no coins */ 730 { 731 struct TALER_PurseContractPublicKeyP other; 732 733 TDB_FILL (other, 734 11); 735 memset (&ctx, 736 0, 737 sizeof (ctx)); 738 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 739 TALER_EXCHANGEDB_iterate_purse_deposits_by_purse (pg, 740 &other, 741 &refund_cb, 742 &ctx)); 743 FAILIF (0 != ctx.total); 744 } 745 return 0; 746 } 747 748 749 /** 750 * The checks to run, in order. 751 */ 752 static const struct TDB_Test tests[] = { 753 { "purse-deposits-empty", 754 &check_empty }, 755 { "purse-deposits-deposit", 756 &check_deposit }, 757 { "purse-deposits-insufficient-balance", 758 &check_insufficient_balance }, 759 { "purse-deposits-too-late", 760 &check_too_late }, 761 { "purse-deposits-iterate", 762 &check_iterate }, 763 { NULL, NULL } 764 }; 765 766 767 int 768 main (int argc, 769 char *const *argv) 770 { 771 int ret; 772 773 ret = TDB_main (argc, 774 argv, 775 "test-purse-deposits", 776 "Tests for the exchangedb `purse_deposits' table", 777 tests); 778 TDB_denom_free (&denom); 779 return ret; 780 } 781 782 783 /* end of test_purse_deposits.c */