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_account_merges.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `account_merges`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_do_reserve_purse(),
23 : * #TALER_EXCHANGEDB_iterate_account_merges_above_serial_id() and
24 : * #TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check().
25 : *
26 : * `account_merges` is the account holder's side of a merge: the request
27 : * the wallet signs to have a purse land in its reserve. do_reserve_purse()
28 : * writes it (together with the `purse_merges` row) and declines when the
29 : * reserve is unknown, when its purse quota is used up, and when the purse
30 : * was already merged into a different reserve.
31 : */
32 : #include "test_common.h"
33 : #include "exchange-database/do_reserve_purse.h"
34 : #include "exchange-database/insert_purse_request.h"
35 : #include "exchange-database/iterate_account_merges_above_serial_id.h"
36 : #include "exchange-database/iterate_merge_amounts_for_kyc_check.h"
37 : #include "exchange-database/rollback.h"
38 : #include "exchange-database/start.h"
39 :
40 :
41 : /**
42 : * Wallet account the reserves of the checks belong to.
43 : */
44 : static struct TDB_Account account;
45 :
46 :
47 : /**
48 : * Build a timestamp from a number of seconds since the epoch.
49 : *
50 : * @param secs seconds since the epoch
51 : * @return the timestamp
52 : */
53 : static struct GNUNET_TIME_Timestamp
54 8 : ts (uint64_t secs)
55 : {
56 8 : struct GNUNET_TIME_Absolute abs = {
57 8 : .abs_value_us = secs * 1000LLU * 1000LLU
58 : };
59 :
60 8 : return GNUNET_TIME_absolute_to_timestamp (abs);
61 : }
62 :
63 :
64 : /**
65 : * Closure for #merge_cb().
66 : */
67 : struct MergeContext
68 : {
69 : /**
70 : * How many rows did the callback see?
71 : */
72 : unsigned int total;
73 :
74 : /**
75 : * Stop after this many rows; 0 for no limit.
76 : */
77 : unsigned int stop_after;
78 :
79 : /**
80 : * Purse we are looking for, NULL to match nothing.
81 : */
82 : const struct TALER_PurseContractPublicKeyP *purse_pub;
83 :
84 : /**
85 : * How many times did we see it?
86 : */
87 : unsigned int matched;
88 :
89 : /**
90 : * Reserve reported for it.
91 : */
92 : struct TALER_ReservePublicKeyP reserve_pub;
93 :
94 : /**
95 : * Amount reported for it.
96 : */
97 : struct TALER_Amount amount;
98 :
99 : /**
100 : * Purse fee reported for it.
101 : */
102 : struct TALER_Amount purse_fee;
103 : };
104 :
105 :
106 : /**
107 : * Callback for
108 : * #TALER_EXCHANGEDB_iterate_account_merges_above_serial_id().
109 : *
110 : * @param cls a `struct MergeContext *`
111 : * @param rowid row of the request
112 : * @param reserve_pub reserve the purse is to land in
113 : * @param purse_pub the purse
114 : * @param h_contract_terms contract of the purse
115 : * @param purse_expiration when the purse expires
116 : * @param amount target amount of the purse
117 : * @param min_age age limit of the purse
118 : * @param flags flags of the purse
119 : * @param purse_fee fee charged to the reserve for the purse
120 : * @param merge_timestamp when the request was made
121 : * @param reserve_sig signature of the account holder
122 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
123 : */
124 : static enum GNUNET_GenericReturnValue
125 2 : merge_cb (void *cls,
126 : uint64_t rowid,
127 : const struct TALER_ReservePublicKeyP *reserve_pub,
128 : const struct TALER_PurseContractPublicKeyP *purse_pub,
129 : const struct TALER_PrivateContractHashP *h_contract_terms,
130 : struct GNUNET_TIME_Timestamp purse_expiration,
131 : const struct TALER_Amount *amount,
132 : uint32_t min_age,
133 : enum TALER_WalletAccountMergeFlags flags,
134 : const struct TALER_Amount *purse_fee,
135 : struct GNUNET_TIME_Timestamp merge_timestamp,
136 : const struct TALER_ReserveSignatureP *reserve_sig)
137 : {
138 2 : struct MergeContext *ctx = cls;
139 :
140 : (void) rowid;
141 : (void) h_contract_terms;
142 : (void) purse_expiration;
143 : (void) min_age;
144 : (void) flags;
145 : (void) merge_timestamp;
146 : (void) reserve_sig;
147 2 : ctx->total++;
148 2 : if ( (NULL != ctx->purse_pub) &&
149 1 : (0 == GNUNET_memcmp (purse_pub,
150 : ctx->purse_pub)) )
151 : {
152 1 : ctx->matched++;
153 1 : ctx->reserve_pub = *reserve_pub;
154 1 : ctx->amount = *amount;
155 1 : ctx->purse_fee = *purse_fee;
156 : }
157 2 : if ( (0 != ctx->stop_after) &&
158 1 : (ctx->total >= ctx->stop_after) )
159 1 : return GNUNET_SYSERR;
160 1 : return GNUNET_OK;
161 : }
162 :
163 :
164 : /**
165 : * Closure for #amount_cb().
166 : */
167 : struct AmountContext
168 : {
169 : /**
170 : * How many amounts did the callback see?
171 : */
172 : unsigned int total;
173 :
174 : /**
175 : * Sum of the whole-unit parts of the amounts seen.
176 : */
177 : uint64_t value_sum;
178 :
179 : /**
180 : * Return this from the callback.
181 : */
182 : enum GNUNET_GenericReturnValue ret;
183 : };
184 :
185 :
186 : /**
187 : * Callback for
188 : * #TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check().
189 : *
190 : * @param cls a `struct AmountContext *`
191 : * @param amount how much was merged
192 : * @param date when it was merged
193 : * @return what @e ret of the closure says
194 : */
195 : static enum GNUNET_GenericReturnValue
196 2 : amount_cb (void *cls,
197 : const struct TALER_Amount *amount,
198 : struct GNUNET_TIME_Absolute date)
199 : {
200 2 : struct AmountContext *ctx = cls;
201 :
202 : (void) date;
203 2 : ctx->total++;
204 2 : ctx->value_sum += amount->value;
205 2 : return ctx->ret;
206 : }
207 :
208 :
209 : /**
210 : * Outcome of a reserve-purse request.
211 : */
212 : struct PurseStatus
213 : {
214 : /**
215 : * Was the purse already merged into another reserve?
216 : */
217 : bool in_conflict;
218 :
219 : /**
220 : * Is the reserve unknown?
221 : */
222 : bool no_reserve;
223 :
224 : /**
225 : * Has the reserve run out of purses?
226 : */
227 : bool insufficient_funds;
228 : };
229 :
230 :
231 : /**
232 : * Ask for a purse to be merged into a reserve.
233 : *
234 : * @param pg the database context
235 : * @param purse purse to merge
236 : * @param reserve_pub reserve to merge it into
237 : * @param seed seed for the signatures
238 : * @param purse_fee fee to charge the reserve, NULL to use the quota
239 : * @param[out] st set to the outcome
240 : * @return transaction status
241 : */
242 : static enum GNUNET_DB_QueryStatus
243 4 : run_reserve_purse (struct TALER_EXCHANGEDB_PostgresContext *pg,
244 : const struct TDB_Purse *purse,
245 : const struct TALER_ReservePublicKeyP *reserve_pub,
246 : uint32_t seed,
247 : const struct TALER_Amount *purse_fee,
248 : struct PurseStatus *st)
249 : {
250 : struct TALER_PurseMergeSignatureP merge_sig;
251 : struct TALER_ReserveSignatureP reserve_sig;
252 :
253 4 : TDB_fill (&merge_sig,
254 : sizeof (merge_sig),
255 : seed);
256 4 : TDB_fill (&reserve_sig,
257 : sizeof (reserve_sig),
258 : seed);
259 4 : memset (st,
260 : 0,
261 : sizeof (*st));
262 4 : return TALER_EXCHANGEDB_do_reserve_purse (pg,
263 : &purse->purse_pub,
264 : &merge_sig,
265 : ts (1600000000),
266 : &reserve_sig,
267 : purse_fee,
268 : reserve_pub,
269 : &st->in_conflict,
270 : &st->no_reserve,
271 : &st->insufficient_funds);
272 : }
273 :
274 :
275 : /**
276 : * Create a purse that records a purse fee, the way the /reserves/$RP/purse
277 : * endpoint does when the account holder offers to pay for the purse.
278 : * TDB_purse() always records a zero fee, and the auditor's view reports
279 : * the fee the *purse* recorded, not the one do_reserve_purse() charged.
280 : *
281 : * @param pg the database context
282 : * @param seed seed for the purse's keys and signature
283 : * @param amount target amount of the purse, e.g. "5"
284 : * @param fee purse fee the account holder offers, e.g. "1"
285 : * @param[out] purse set to the purse
286 : */
287 : static void
288 1 : make_purse (struct TALER_EXCHANGEDB_PostgresContext *pg,
289 : uint32_t seed,
290 : const char *amount,
291 : const char *fee,
292 : struct TDB_Purse *purse)
293 : {
294 1 : struct TALER_Amount purse_fee = TDB_amount (fee);
295 : enum GNUNET_DB_QueryStatus qs;
296 1 : bool in_conflict = false;
297 :
298 1 : memset (purse,
299 : 0,
300 : sizeof (*purse));
301 1 : TDB_fill (&purse->purse_pub,
302 : sizeof (purse->purse_pub),
303 : seed);
304 1 : TDB_fill (&purse->merge_pub,
305 : sizeof (purse->merge_pub),
306 : seed);
307 1 : TDB_fill (&purse->h_contract_terms,
308 : sizeof (purse->h_contract_terms),
309 : seed);
310 1 : TDB_fill (&purse->purse_sig,
311 : sizeof (purse->purse_sig),
312 : seed);
313 1 : purse->purse_expiration = ts (1700000000);
314 1 : purse->amount = TDB_amount (amount);
315 1 : qs = TALER_EXCHANGEDB_insert_purse_request (
316 : pg,
317 1 : &purse->purse_pub,
318 1 : &purse->merge_pub,
319 : purse->purse_expiration,
320 1 : &purse->h_contract_terms,
321 : 0,
322 : TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE,
323 : &purse_fee,
324 1 : &purse->amount,
325 1 : &purse->purse_sig,
326 : &in_conflict);
327 1 : GNUNET_assert (0 <= qs);
328 1 : GNUNET_assert (! in_conflict);
329 1 : }
330 :
331 :
332 : /**
333 : * Nothing is reported while the table is empty.
334 : *
335 : * @param pg the database context
336 : * @return 0 on success
337 : */
338 : static int
339 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
340 : {
341 1 : struct MergeContext ctx = { 0 };
342 1 : struct AmountContext actx = { 0 };
343 :
344 1 : TDB_account (pg,
345 : 10,
346 : &account);
347 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
348 : TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
349 : pg,
350 : 0,
351 : &merge_cb,
352 : &ctx));
353 1 : actx.ret = GNUNET_OK;
354 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
355 : TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
356 : pg,
357 : &account.h_normalized,
358 : GNUNET_TIME_UNIT_ZERO_ABS,
359 : &amount_cb,
360 : &actx));
361 1 : FAILIF (0 != ctx.total);
362 1 : FAILIF (0 != actx.total);
363 1 : return 0;
364 : }
365 :
366 :
367 : /**
368 : * A reserve that does not exist cannot take a purse.
369 : *
370 : * @param pg the database context
371 : * @return 0 on success
372 : */
373 : static int
374 1 : check_no_reserve (struct TALER_EXCHANGEDB_PostgresContext *pg)
375 : {
376 : struct TDB_Purse purse;
377 : struct TALER_ReservePublicKeyP reserve_pub;
378 1 : struct TALER_Amount purse_fee = TDB_amount ("1");
379 : struct PurseStatus st;
380 :
381 1 : TDB_purse (pg,
382 : 9,
383 : "5",
384 : ts (1700000000),
385 : &purse);
386 1 : TDB_FILL (reserve_pub,
387 : 99);
388 : /* A non-zero purse fee is what makes an unknown reserve fatal: with a
389 : zero fee the function creates the reserve instead. The `purse_merges`
390 : row goes in before the reserve is looked at, so the caller has to roll
391 : back -- which is what the /purses/.../merge handler does. */
392 1 : FAILIF (GNUNET_OK !=
393 : TALER_EXCHANGEDB_start (pg,
394 : "test-account-merges-no-reserve"));
395 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
396 : run_reserve_purse (pg,
397 : &purse,
398 : &reserve_pub,
399 : 9,
400 : &purse_fee,
401 : &st),
402 : TALER_EXCHANGEDB_rollback (pg));
403 1 : TALER_EXCHANGEDB_rollback (pg);
404 1 : FAILIF (! st.no_reserve);
405 1 : FAILIF (! st.insufficient_funds);
406 1 : FAILIF (0 != TDB_count (pg,
407 : "FROM account_merges"));
408 1 : FAILIF (0 != TDB_count (pg,
409 : "FROM purse_merges"));
410 1 : return 0;
411 : }
412 :
413 :
414 : /**
415 : * Paying the purse fee from the reserve creates the merge request.
416 : *
417 : * @param pg the database context
418 : * @return 0 on success
419 : */
420 : static int
421 1 : check_reserve_purse (struct TALER_EXCHANGEDB_PostgresContext *pg)
422 : {
423 : struct TDB_Purse purse;
424 : struct TALER_ReservePublicKeyP reserve_pub;
425 1 : struct TALER_Amount purse_fee = TDB_amount ("1");
426 1 : struct TALER_Amount expect_balance = TDB_amount ("9");
427 : struct MergeContext ctx;
428 : struct PurseStatus st;
429 : char *hex;
430 :
431 1 : make_purse (pg,
432 : 10,
433 : "5",
434 : "1",
435 : &purse);
436 1 : TDB_reserve (pg,
437 : 10,
438 : "10",
439 : &reserve_pub);
440 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
441 : run_reserve_purse (pg,
442 : &purse,
443 : &reserve_pub,
444 : 10,
445 : &purse_fee,
446 : &st));
447 1 : FAILIF (st.no_reserve);
448 1 : FAILIF (st.in_conflict);
449 1 : FAILIF (st.insufficient_funds);
450 1 : FAILIF (1 != TDB_count (pg,
451 : "FROM account_merges"));
452 : /* the merge is also recorded from the purse's side */
453 1 : FAILIF (1 != TDB_count (pg,
454 : "FROM purse_merges"));
455 : /* the reserve paid the purse fee */
456 1 : hex = TDB_hex (&reserve_pub,
457 : sizeof (reserve_pub));
458 1 : FAILIF_C (1 != TDB_count (pg,
459 : "FROM reserves"
460 : " WHERE reserve_pub=decode('%s','hex')"
461 : " AND current_balance=ROW(9,0)::taler_amount",
462 : hex),
463 : GNUNET_free (hex));
464 1 : GNUNET_free (hex);
465 : (void) expect_balance;
466 :
467 1 : memset (&ctx,
468 : 0,
469 : sizeof (ctx));
470 1 : ctx.purse_pub = &purse.purse_pub;
471 1 : FAILIF (0 >=
472 : TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
473 : pg,
474 : 0,
475 : &merge_cb,
476 : &ctx));
477 1 : FAILIF (1 != ctx.matched);
478 1 : FAILIF (0 != GNUNET_memcmp (&ctx.reserve_pub,
479 : &reserve_pub));
480 1 : FAILIF (0 != TALER_amount_cmp (&ctx.amount,
481 : &purse.amount));
482 1 : FAILIF (0 != TALER_amount_cmp (&ctx.purse_fee,
483 : &purse_fee));
484 :
485 : /* the same purse into a different reserve is a conflict */
486 : {
487 : struct TALER_ReservePublicKeyP other;
488 :
489 1 : TDB_reserve (pg,
490 : 11,
491 : "10",
492 : &other);
493 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
494 : run_reserve_purse (pg,
495 : &purse,
496 : &other,
497 : 11,
498 : &purse_fee,
499 : &st));
500 1 : FAILIF (! st.in_conflict);
501 1 : FAILIF (1 != TDB_count (pg,
502 : "FROM account_merges"));
503 : }
504 1 : return 0;
505 : }
506 :
507 :
508 : /**
509 : * A reserve that cannot pay the purse fee is refused.
510 : *
511 : * @param pg the database context
512 : * @return 0 on success
513 : */
514 : static int
515 1 : check_insufficient_funds (struct TALER_EXCHANGEDB_PostgresContext *pg)
516 : {
517 : struct TDB_Purse purse;
518 : struct TALER_ReservePublicKeyP reserve_pub;
519 1 : struct TALER_Amount purse_fee = TDB_amount ("5");
520 : struct PurseStatus st;
521 :
522 1 : TDB_purse (pg,
523 : 12,
524 : "5",
525 : ts (1700000000),
526 : &purse);
527 1 : TDB_reserve (pg,
528 : 12,
529 : "1",
530 : &reserve_pub);
531 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
532 : run_reserve_purse (pg,
533 : &purse,
534 : &reserve_pub,
535 : 12,
536 : &purse_fee,
537 : &st));
538 1 : FAILIF (! st.insufficient_funds);
539 1 : FAILIF (1 != TDB_count (pg,
540 : "FROM account_merges"));
541 1 : return 0;
542 : }
543 :
544 :
545 : /**
546 : * The wallet's KYC view sums the merges that landed in its account.
547 : *
548 : * The account in question is the reserve's own `taler-reserve://` account,
549 : * which is what do_reserve_purse() records in `wallet_h_payto` -- not a
550 : * bank account.
551 : *
552 : * @param pg the database context
553 : * @return 0 on success
554 : */
555 : static int
556 1 : check_kyc_amounts (struct TALER_EXCHANGEDB_PostgresContext *pg)
557 : {
558 : struct AmountContext ctx;
559 : struct TALER_ReservePublicKeyP reserve_pub;
560 : struct TALER_NormalizedPayto payto;
561 : struct TALER_NormalizedPaytoHashP h_payto;
562 : struct TALER_NormalizedPaytoHashP other;
563 :
564 1 : TDB_fill (&reserve_pub,
565 : sizeof (reserve_pub),
566 : 10);
567 1 : payto = TALER_reserve_make_payto (pg->exchange_url,
568 : &reserve_pub);
569 1 : TALER_normalized_payto_hash (payto,
570 : &h_payto);
571 1 : GNUNET_free (payto.normalized_payto);
572 :
573 : /* the merge of check_reserve_purse() is charged to our account, but the
574 : purse has not been decided yet, so the KYC view has nothing */
575 1 : memset (&ctx,
576 : 0,
577 : sizeof (ctx));
578 1 : ctx.ret = GNUNET_OK;
579 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
580 : TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
581 : pg,
582 : &h_payto,
583 : GNUNET_TIME_UNIT_ZERO_ABS,
584 : &amount_cb,
585 : &ctx));
586 1 : FAILIF (0 != ctx.total);
587 :
588 : /* once the purse is decided in favour of the reserve, it counts */
589 1 : FAILIF (GNUNET_OK !=
590 : TDB_exec (pg,
591 : "INSERT INTO purse_decision"
592 : " (purse_pub,action_timestamp,refunded)"
593 : " SELECT purse_pub,1000000,FALSE"
594 : " FROM account_merges;"));
595 1 : memset (&ctx,
596 : 0,
597 : sizeof (ctx));
598 1 : ctx.ret = GNUNET_OK;
599 1 : FAILIF (0 >=
600 : TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
601 : pg,
602 : &h_payto,
603 : GNUNET_TIME_UNIT_ZERO_ABS,
604 : &amount_cb,
605 : &ctx));
606 1 : FAILIF (0 == ctx.total);
607 1 : FAILIF (0 == ctx.value_sum);
608 :
609 : /* an account nobody merged into has nothing */
610 1 : TDB_FILL (other,
611 : 97);
612 1 : memset (&ctx,
613 : 0,
614 : sizeof (ctx));
615 1 : ctx.ret = GNUNET_OK;
616 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
617 : TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
618 : pg,
619 : &other,
620 : GNUNET_TIME_UNIT_ZERO_ABS,
621 : &amount_cb,
622 : &ctx));
623 1 : FAILIF (0 != ctx.total);
624 :
625 : /* a time limit past the merges hides them */
626 1 : memset (&ctx,
627 : 0,
628 : sizeof (ctx));
629 1 : ctx.ret = GNUNET_OK;
630 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
631 : TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
632 : pg,
633 : &h_payto,
634 : ts (1700000000).abs_time,
635 : &amount_cb,
636 : &ctx));
637 1 : FAILIF (0 != ctx.total);
638 :
639 : /* and an aborting callback is not an error */
640 1 : memset (&ctx,
641 : 0,
642 : sizeof (ctx));
643 1 : ctx.ret = GNUNET_NO;
644 1 : FAILIF (0 >
645 : TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
646 : pg,
647 : &h_payto,
648 : GNUNET_TIME_UNIT_ZERO_ABS,
649 : &amount_cb,
650 : &ctx));
651 1 : FAILIF (1 != ctx.total);
652 1 : return 0;
653 : }
654 :
655 :
656 : /**
657 : * The iterator's bound and abort return behave as documented.
658 : *
659 : * @param pg the database context
660 : * @return 0 on success
661 : */
662 : static int
663 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
664 : {
665 : struct MergeContext ctx;
666 :
667 1 : memset (&ctx,
668 : 0,
669 : sizeof (ctx));
670 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
671 : TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
672 : pg,
673 : 1000,
674 : &merge_cb,
675 : &ctx));
676 1 : FAILIF (0 != ctx.total);
677 1 : memset (&ctx,
678 : 0,
679 : sizeof (ctx));
680 1 : ctx.stop_after = 1;
681 1 : FAILIF (0 >=
682 : TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
683 : pg,
684 : 0,
685 : &merge_cb,
686 : &ctx));
687 1 : FAILIF (1 != ctx.total);
688 1 : return 0;
689 : }
690 :
691 :
692 : /**
693 : * The checks to run, in order.
694 : */
695 : static const struct TDB_Test tests[] = {
696 : { "account-merges-empty",
697 : &check_empty },
698 : { "account-merges-no-reserve",
699 : &check_no_reserve },
700 : { "account-merges-reserve-purse",
701 : &check_reserve_purse },
702 : { "account-merges-insufficient-funds",
703 : &check_insufficient_funds },
704 : { "account-merges-kyc-amounts",
705 : &check_kyc_amounts },
706 : { "account-merges-iterate",
707 : &check_iterate },
708 : { NULL, NULL }
709 : };
710 :
711 :
712 : int
713 1 : main (int argc,
714 : char *const *argv)
715 : {
716 : int ret;
717 :
718 1 : ret = TDB_main (argc,
719 : argv,
720 : "test-account-merges",
721 : "Tests for the exchangedb `account_merges' table",
722 : tests);
723 1 : TDB_account_free (&account);
724 1 : return ret;
725 : }
726 :
727 :
728 : /* end of test_account_merges.c */
|