Line data Source code
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_merges.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `purse_merges`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_do_purse_merge(),
23 : * #TALER_EXCHANGEDB_get_purse_merge(),
24 : * #TALER_EXCHANGEDB_iterate_purse_merges_above_serial_id(),
25 : * #TALER_EXCHANGEDB_iterate_wallet_merges().
26 : *
27 : * A merge is the exchange acting on the merge capability key: it moves a
28 : * paid-up purse into a reserve. do_purse_merge() therefore declines when
29 : * the purse is not paid up and when the named partner exchange is unknown,
30 : * and reports a conflict when the purse was already merged elsewhere.
31 : */
32 : #include "test_common.h"
33 : #include "exchange-database/do_purse_deposit.h"
34 : #include "exchange-database/do_purse_merge.h"
35 : #include "exchange-database/get_purse_merge.h"
36 : #include "exchange-database/get_reserve.h"
37 : #include "exchange-database/iterate_purse_merges_above_serial_id.h"
38 : #include "exchange-database/iterate_wallet_merges.h"
39 :
40 :
41 : /**
42 : * Denomination the checks pay purses with.
43 : */
44 : static struct TDB_Denom denom;
45 :
46 :
47 : /**
48 : * Wallet account the reserve of the checks belongs to.
49 : */
50 : static struct TDB_Account account;
51 :
52 :
53 : /**
54 : * Build a timestamp from a number of seconds since the epoch.
55 : *
56 : * @param secs seconds since the epoch
57 : * @return the timestamp
58 : */
59 : static struct GNUNET_TIME_Timestamp
60 8 : ts (uint64_t secs)
61 : {
62 8 : struct GNUNET_TIME_Absolute abs = {
63 8 : .abs_value_us = secs * 1000LLU * 1000LLU
64 : };
65 :
66 8 : return GNUNET_TIME_absolute_to_timestamp (abs);
67 : }
68 :
69 :
70 : /**
71 : * Closure for #merge_cb().
72 : */
73 : struct MergeContext
74 : {
75 : /**
76 : * How many rows did the callback see?
77 : */
78 : unsigned int total;
79 :
80 : /**
81 : * Stop after this many rows; 0 for no limit.
82 : */
83 : unsigned int stop_after;
84 :
85 : /**
86 : * Purse we are looking for, NULL to match nothing.
87 : */
88 : const struct TALER_PurseContractPublicKeyP *purse_pub;
89 :
90 : /**
91 : * How many times did we see it?
92 : */
93 : unsigned int matched;
94 :
95 : /**
96 : * Reserve reported for it.
97 : */
98 : struct TALER_ReservePublicKeyP reserve_pub;
99 :
100 : /**
101 : * Amount reported for it.
102 : */
103 : struct TALER_Amount amount;
104 :
105 : /**
106 : * Was a partner exchange reported for it?
107 : */
108 : bool have_partner;
109 : };
110 :
111 :
112 : /**
113 : * Callback for
114 : * #TALER_EXCHANGEDB_iterate_purse_merges_above_serial_id().
115 : *
116 : * @param cls a `struct MergeContext *`
117 : * @param rowid row of the merge
118 : * @param partner_base_url partner exchange, NULL for us
119 : * @param amount target amount of the purse
120 : * @param balance balance of the purse
121 : * @param flags flags of the purse
122 : * @param merge_pub merge capability key
123 : * @param reserve_pub reserve the purse went into
124 : * @param merge_sig signature affirming the merge
125 : * @param purse_pub the purse
126 : * @param merge_timestamp when it was merged
127 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
128 : */
129 : static enum GNUNET_GenericReturnValue
130 2 : merge_cb (void *cls,
131 : uint64_t rowid,
132 : const char *partner_base_url,
133 : const struct TALER_Amount *amount,
134 : const struct TALER_Amount *balance,
135 : enum TALER_WalletAccountMergeFlags flags,
136 : const struct TALER_PurseMergePublicKeyP *merge_pub,
137 : const struct TALER_ReservePublicKeyP *reserve_pub,
138 : const struct TALER_PurseMergeSignatureP *merge_sig,
139 : const struct TALER_PurseContractPublicKeyP *purse_pub,
140 : struct GNUNET_TIME_Timestamp merge_timestamp)
141 : {
142 2 : struct MergeContext *ctx = cls;
143 :
144 : (void) rowid;
145 : (void) balance;
146 : (void) flags;
147 : (void) merge_pub;
148 : (void) merge_sig;
149 : (void) merge_timestamp;
150 2 : ctx->total++;
151 2 : if ( (NULL != ctx->purse_pub) &&
152 1 : (0 == GNUNET_memcmp (purse_pub,
153 : ctx->purse_pub)) )
154 : {
155 1 : ctx->matched++;
156 1 : ctx->reserve_pub = *reserve_pub;
157 1 : ctx->amount = *amount;
158 1 : ctx->have_partner = (NULL != partner_base_url);
159 : }
160 2 : if ( (0 != ctx->stop_after) &&
161 1 : (ctx->total >= ctx->stop_after) )
162 1 : return GNUNET_SYSERR;
163 1 : return GNUNET_OK;
164 : }
165 :
166 :
167 : /**
168 : * Closure for #transfer_cb() and #amount_cb().
169 : */
170 : struct SumContext
171 : {
172 : /**
173 : * How many entries did the callback see?
174 : */
175 : unsigned int total;
176 :
177 : /**
178 : * Sum of the whole-unit parts of the amounts seen.
179 : */
180 : uint64_t value_sum;
181 :
182 : };
183 :
184 :
185 : /**
186 : * Callback for #TALER_EXCHANGEDB_iterate_wallet_merges().
187 : *
188 : * @param cls a `struct SumContext *`
189 : * @param row_id row of the merge
190 : * @param payto_uri wallet account involved
191 : * @param execution_time when the merge happened
192 : * @param amount how much was merged
193 : */
194 : static void
195 3 : transfer_cb (void *cls,
196 : uint64_t row_id,
197 : const char *payto_uri,
198 : struct GNUNET_TIME_Absolute execution_time,
199 : const struct TALER_Amount *amount)
200 : {
201 3 : struct SumContext *ctx = cls;
202 :
203 : (void) row_id;
204 : (void) payto_uri;
205 : (void) execution_time;
206 3 : ctx->total++;
207 3 : ctx->value_sum += amount->value;
208 3 : }
209 :
210 :
211 : /**
212 : * Outcome of a merge.
213 : */
214 : struct MergeStatus
215 : {
216 : /**
217 : * Was the named partner exchange unknown?
218 : */
219 : bool no_partner;
220 :
221 : /**
222 : * Was the purse not paid up?
223 : */
224 : bool no_balance;
225 :
226 : /**
227 : * Was the purse already merged elsewhere?
228 : */
229 : bool in_conflict;
230 : };
231 :
232 :
233 : /**
234 : * Merge a purse into a reserve.
235 : *
236 : * @param pg the database context
237 : * @param purse purse to merge
238 : * @param reserve_pub reserve to credit
239 : * @param h_payto normalized account of the reserve's wallet
240 : * @param seed seed for the merge and reserve signatures
241 : * @param partner_url partner exchange, NULL for us
242 : * @param[out] st set to the outcome
243 : * @return transaction status
244 : */
245 : static enum GNUNET_DB_QueryStatus
246 4 : run_merge (struct TALER_EXCHANGEDB_PostgresContext *pg,
247 : const struct TDB_Purse *purse,
248 : const struct TALER_ReservePublicKeyP *reserve_pub,
249 : uint32_t seed,
250 : const char *partner_url,
251 : struct MergeStatus *st)
252 : {
253 : struct TALER_PurseMergeSignatureP merge_sig;
254 : struct TALER_ReserveSignatureP reserve_sig;
255 :
256 4 : TDB_fill (&merge_sig,
257 : sizeof (merge_sig),
258 : seed);
259 4 : TDB_fill (&reserve_sig,
260 : sizeof (reserve_sig),
261 : seed);
262 4 : memset (st,
263 : 0,
264 : sizeof (*st));
265 4 : return TALER_EXCHANGEDB_do_purse_merge (pg,
266 : &purse->purse_pub,
267 : &merge_sig,
268 : ts (1600000000),
269 : &reserve_sig,
270 : partner_url,
271 : reserve_pub,
272 : &st->no_partner,
273 : &st->no_balance,
274 : &st->in_conflict);
275 : }
276 :
277 :
278 : /**
279 : * Pay a purse up to its target amount.
280 : *
281 : * @param pg the database context
282 : * @param purse purse to fill
283 : * @param seed seed for the coin
284 : * @param amount how much to pay in, e.g. "5"
285 : */
286 : static void
287 2 : fill_purse (struct TALER_EXCHANGEDB_PostgresContext *pg,
288 : const struct TDB_Purse *purse,
289 : uint32_t seed,
290 : const char *amount)
291 : {
292 : struct TALER_CoinPublicInfo coin;
293 : struct TALER_CoinSpendSignatureP coin_sig;
294 2 : struct TALER_Amount a = TDB_amount (amount);
295 : bool balance_ok;
296 : bool too_late;
297 : bool conflict;
298 :
299 2 : TDB_coin (pg,
300 : &denom,
301 : seed,
302 : &coin,
303 : NULL);
304 2 : TDB_fill (&coin_sig,
305 : sizeof (coin_sig),
306 : seed);
307 2 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
308 : TALER_EXCHANGEDB_do_purse_deposit (pg,
309 : &purse->purse_pub,
310 : &coin.coin_pub,
311 : &a,
312 : &coin_sig,
313 : &a,
314 : &balance_ok,
315 : &too_late,
316 : &conflict));
317 2 : GNUNET_assert (balance_ok);
318 2 : TDB_coin_free (&coin);
319 2 : }
320 :
321 :
322 : /**
323 : * Nothing is reported while the table is empty.
324 : *
325 : * @param pg the database context
326 : * @return 0 on success
327 : */
328 : static int
329 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
330 : {
331 : struct TALER_PurseContractPublicKeyP purse_pub;
332 : struct TALER_PurseMergeSignatureP merge_sig;
333 : struct GNUNET_TIME_Timestamp merge_timestamp;
334 : struct TALER_ReservePublicKeyP reserve_pub;
335 1 : struct MergeContext ctx = { 0 };
336 1 : struct SumContext sctx = { 0 };
337 1 : struct TALER_Amount zero = TDB_amount ("0");
338 1 : char *partner_url = NULL;
339 : bool refunded;
340 :
341 1 : TDB_denom (pg,
342 : 10,
343 : "5",
344 : "0.1",
345 : &denom);
346 1 : TDB_account (pg,
347 : 10,
348 : &account);
349 1 : TDB_FILL (purse_pub,
350 : 1);
351 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
352 : TALER_EXCHANGEDB_get_purse_merge (pg,
353 : &purse_pub,
354 : &merge_sig,
355 : &merge_timestamp,
356 : &partner_url,
357 : &reserve_pub,
358 : &refunded));
359 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
360 : TALER_EXCHANGEDB_iterate_purse_merges_above_serial_id (
361 : pg,
362 : 0,
363 : &merge_cb,
364 : &ctx));
365 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
366 : TALER_EXCHANGEDB_iterate_wallet_merges (pg,
367 : &zero,
368 : 0,
369 : 10,
370 : NULL,
371 : &transfer_cb,
372 : &sctx));
373 1 : FAILIF (0 != ctx.total);
374 1 : FAILIF (0 != sctx.total);
375 1 : return 0;
376 : }
377 :
378 :
379 : /**
380 : * A purse that has not been paid up cannot be merged.
381 : *
382 : * @param pg the database context
383 : * @return 0 on success
384 : */
385 : static int
386 1 : check_no_balance (struct TALER_EXCHANGEDB_PostgresContext *pg)
387 : {
388 : struct TDB_Purse purse;
389 : struct TALER_ReservePublicKeyP reserve_pub;
390 : struct MergeStatus st;
391 :
392 1 : TDB_purse (pg,
393 : 10,
394 : "5",
395 : ts (1700000000),
396 : &purse);
397 1 : TDB_reserve (pg,
398 : 10,
399 : "0",
400 : &reserve_pub);
401 : /* only EUR:1 of the EUR:5 has been paid in */
402 1 : fill_purse (pg,
403 : &purse,
404 : 20,
405 : "1");
406 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
407 : run_merge (pg,
408 : &purse,
409 : &reserve_pub,
410 : 10,
411 : NULL,
412 : &st));
413 1 : FAILIF (! st.no_balance);
414 1 : FAILIF (st.no_partner);
415 1 : FAILIF (st.in_conflict);
416 1 : FAILIF (0 != TDB_count (pg,
417 : "FROM purse_merges"));
418 1 : return 0;
419 : }
420 :
421 :
422 : /**
423 : * A partner exchange the exchange does not know cannot receive a merge.
424 : *
425 : * @param pg the database context
426 : * @return 0 on success
427 : */
428 : static int
429 1 : check_no_partner (struct TALER_EXCHANGEDB_PostgresContext *pg)
430 : {
431 : struct TDB_Purse purse;
432 : struct TALER_ReservePublicKeyP reserve_pub;
433 : struct MergeStatus st;
434 :
435 1 : TDB_purse (pg,
436 : 10,
437 : "5",
438 : ts (1700000000),
439 : &purse);
440 1 : TDB_fill (&reserve_pub,
441 : sizeof (reserve_pub),
442 : 10);
443 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
444 : run_merge (pg,
445 : &purse,
446 : &reserve_pub,
447 : 10,
448 : "https://partner.example/",
449 : &st));
450 1 : FAILIF (! st.no_partner);
451 1 : FAILIF (0 != TDB_count (pg,
452 : "FROM purse_merges"));
453 1 : return 0;
454 : }
455 :
456 :
457 : /**
458 : * A paid-up purse is merged into the reserve, which is credited.
459 : *
460 : * @param pg the database context
461 : * @return 0 on success
462 : */
463 : static int
464 1 : check_merge (struct TALER_EXCHANGEDB_PostgresContext *pg)
465 : {
466 : struct TDB_Purse purse;
467 : struct TALER_ReservePublicKeyP reserve_pub;
468 : struct TALER_ReservePublicKeyP got_reserve;
469 : struct TALER_EXCHANGEDB_Reserve reserve;
470 : struct MergeStatus st;
471 : struct MergeContext ctx;
472 : struct TALER_PurseMergeSignatureP merge_sig;
473 : struct TALER_PurseMergeSignatureP expect_sig;
474 : struct GNUNET_TIME_Timestamp merge_timestamp;
475 1 : struct TALER_Amount expect_balance = TDB_amount ("5");
476 1 : char *partner_url = NULL;
477 1 : bool refunded = true;
478 :
479 1 : TDB_purse (pg,
480 : 10,
481 : "5",
482 : ts (1700000000),
483 : &purse);
484 1 : TDB_fill (&reserve_pub,
485 : sizeof (reserve_pub),
486 : 10);
487 : /* pay in the remaining EUR:4 */
488 1 : fill_purse (pg,
489 : &purse,
490 : 21,
491 : "4");
492 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
493 : run_merge (pg,
494 : &purse,
495 : &reserve_pub,
496 : 10,
497 : NULL,
498 : &st));
499 1 : FAILIF (st.no_balance);
500 1 : FAILIF (st.no_partner);
501 1 : FAILIF (st.in_conflict);
502 1 : FAILIF (1 != TDB_count (pg,
503 : "FROM purse_merges"));
504 :
505 : /* the reserve got the money */
506 1 : memset (&reserve,
507 : 0,
508 : sizeof (reserve));
509 1 : reserve.pub = reserve_pub;
510 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
511 : TALER_EXCHANGEDB_get_reserve (pg,
512 : &reserve));
513 1 : FAILIF (0 != TALER_amount_cmp (&reserve.balance,
514 : &expect_balance));
515 :
516 1 : TDB_FILL (expect_sig,
517 : 10);
518 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
519 : TALER_EXCHANGEDB_get_purse_merge (pg,
520 : &purse.purse_pub,
521 : &merge_sig,
522 : &merge_timestamp,
523 : &partner_url,
524 : &got_reserve,
525 : &refunded));
526 1 : FAILIF (0 != GNUNET_memcmp (&merge_sig,
527 : &expect_sig));
528 1 : FAILIF (0 != GNUNET_memcmp (&got_reserve,
529 : &reserve_pub));
530 1 : FAILIF (GNUNET_TIME_timestamp_cmp (merge_timestamp,
531 : !=,
532 : ts (1600000000)));
533 1 : FAILIF (refunded);
534 : /* the purse stayed with us, so there is no partner exchange */
535 1 : FAILIF (NULL != partner_url);
536 :
537 1 : memset (&ctx,
538 : 0,
539 : sizeof (ctx));
540 1 : ctx.purse_pub = &purse.purse_pub;
541 1 : FAILIF (0 >=
542 : TALER_EXCHANGEDB_iterate_purse_merges_above_serial_id (
543 : pg,
544 : 0,
545 : &merge_cb,
546 : &ctx));
547 1 : FAILIF (1 != ctx.matched);
548 1 : FAILIF (0 != GNUNET_memcmp (&ctx.reserve_pub,
549 : &reserve_pub));
550 1 : FAILIF (0 != TALER_amount_cmp (&ctx.amount,
551 : &expect_balance));
552 1 : FAILIF (ctx.have_partner);
553 :
554 : /* merging the same purse into a different reserve is a conflict */
555 : {
556 : struct TALER_ReservePublicKeyP other;
557 :
558 1 : TDB_reserve (pg,
559 : 12,
560 : "0",
561 : &other);
562 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
563 : run_merge (pg,
564 : &purse,
565 : &other,
566 : 12,
567 : NULL,
568 : &st));
569 1 : FAILIF (! st.in_conflict);
570 1 : FAILIF (1 != TDB_count (pg,
571 : "FROM purse_merges"));
572 : }
573 1 : return 0;
574 : }
575 :
576 :
577 : /**
578 : * The wallet's view of its merges reports the purse once the reserve is
579 : * tied to a wallet account.
580 : *
581 : * @param pg the database context
582 : * @return 0 on success
583 : */
584 : static int
585 1 : check_wallet_merges (struct TALER_EXCHANGEDB_PostgresContext *pg)
586 : {
587 : struct TALER_ReservePublicKeyP reserve_pub;
588 1 : struct TALER_Amount zero = TDB_amount ("0");
589 1 : struct TALER_Amount huge = TDB_amount ("1000");
590 : struct SumContext ctx;
591 : char *rhex;
592 : char *nhex;
593 :
594 : /* Until the reserve is known to belong to a wallet account, the view
595 : has nothing to join against. */
596 1 : memset (&ctx,
597 : 0,
598 : sizeof (ctx));
599 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
600 : TALER_EXCHANGEDB_iterate_wallet_merges (pg,
601 : &zero,
602 : 0,
603 : 10,
604 : NULL,
605 : &transfer_cb,
606 : &ctx));
607 1 : FAILIF (0 != ctx.total);
608 :
609 1 : TDB_fill (&reserve_pub,
610 : sizeof (reserve_pub),
611 : 10);
612 1 : rhex = TDB_hex (&reserve_pub,
613 : sizeof (reserve_pub));
614 1 : nhex = TDB_hex (&account.h_normalized,
615 : sizeof (account.h_normalized));
616 1 : FAILIF_C (GNUNET_OK !=
617 : TDB_exec (pg,
618 : "UPDATE kyc_targets"
619 : " SET target_pub=decode('%s','hex')"
620 : " ,is_wallet=TRUE"
621 : " WHERE h_normalized_payto=decode('%s','hex');",
622 : rhex,
623 : nhex),
624 : GNUNET_free (rhex); GNUNET_free (nhex));
625 1 : GNUNET_free (rhex);
626 1 : GNUNET_free (nhex);
627 :
628 1 : memset (&ctx,
629 : 0,
630 : sizeof (ctx));
631 1 : FAILIF (1 !=
632 : TALER_EXCHANGEDB_iterate_wallet_merges (pg,
633 : &zero,
634 : 0,
635 : 10,
636 : NULL,
637 : &transfer_cb,
638 : &ctx));
639 1 : FAILIF (5 != ctx.value_sum);
640 :
641 : /* filtering by that very account keeps it */
642 1 : memset (&ctx,
643 : 0,
644 : sizeof (ctx));
645 1 : FAILIF (1 !=
646 : TALER_EXCHANGEDB_iterate_wallet_merges (pg,
647 : &zero,
648 : 0,
649 : 10,
650 : &account.h_normalized,
651 : &transfer_cb,
652 : &ctx));
653 1 : FAILIF (1 != ctx.total);
654 :
655 : /* a threshold nobody meets drops it */
656 1 : memset (&ctx,
657 : 0,
658 : sizeof (ctx));
659 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
660 : TALER_EXCHANGEDB_iterate_wallet_merges (pg,
661 : &huge,
662 : 0,
663 : 10,
664 : NULL,
665 : &transfer_cb,
666 : &ctx));
667 1 : FAILIF (0 != ctx.total);
668 :
669 : /* and the negative limit walks the table the other way */
670 1 : memset (&ctx,
671 : 0,
672 : sizeof (ctx));
673 1 : FAILIF (1 !=
674 : TALER_EXCHANGEDB_iterate_wallet_merges (pg,
675 : &zero,
676 : 1000,
677 : -10,
678 : NULL,
679 : &transfer_cb,
680 : &ctx));
681 1 : FAILIF (1 != ctx.total);
682 1 : return 0;
683 : }
684 :
685 :
686 : /**
687 : * The iterators' bounds and abort return behave as documented.
688 : *
689 : * @param pg the database context
690 : * @return 0 on success
691 : */
692 : static int
693 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
694 : {
695 : struct MergeContext ctx;
696 :
697 1 : memset (&ctx,
698 : 0,
699 : sizeof (ctx));
700 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
701 : TALER_EXCHANGEDB_iterate_purse_merges_above_serial_id (
702 : pg,
703 : 1000,
704 : &merge_cb,
705 : &ctx));
706 1 : FAILIF (0 != ctx.total);
707 1 : memset (&ctx,
708 : 0,
709 : sizeof (ctx));
710 1 : ctx.stop_after = 1;
711 1 : FAILIF (1 !=
712 : TALER_EXCHANGEDB_iterate_purse_merges_above_serial_id (
713 : pg,
714 : 0,
715 : &merge_cb,
716 : &ctx));
717 1 : FAILIF (1 != ctx.total);
718 1 : return 0;
719 : }
720 :
721 :
722 : /**
723 : * The checks to run, in order.
724 : */
725 : static const struct TDB_Test tests[] = {
726 : { "purse-merges-empty",
727 : &check_empty },
728 : { "purse-merges-no-balance",
729 : &check_no_balance },
730 : { "purse-merges-no-partner",
731 : &check_no_partner },
732 : { "purse-merges-merge",
733 : &check_merge },
734 : { "purse-merges-wallet-merges",
735 : &check_wallet_merges },
736 : { "purse-merges-iterate",
737 : &check_iterate },
738 : { NULL, NULL }
739 : };
740 :
741 :
742 : int
743 1 : main (int argc,
744 : char *const *argv)
745 : {
746 : int ret;
747 :
748 1 : ret = TDB_main (argc,
749 : argv,
750 : "test-purse-merges",
751 : "Tests for the exchangedb `purse_merges' table",
752 : tests);
753 1 : TDB_account_free (&account);
754 1 : TDB_denom_free (&denom);
755 1 : return ret;
756 : }
757 :
758 :
759 : /* end of test_purse_merges.c */
|