test_aml_history.c (27838B)
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_aml_history.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `aml_history` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_insert_aml_decision(), 23 * #TALER_EXCHANGEDB_iterate_aml_history(), 24 * #TALER_EXCHANGEDB_iterate_aml_history_above_serial_id(), 25 * #TALER_EXCHANGEDB_iterate_aml_decisions() and 26 * #TALER_EXCHANGEDB_aml_history_builder(). 27 * 28 * `aml_history` is what an AML officer signed: one row per decision, each 29 * pointing at the `legitimization_outcomes` row it produced. The insert 30 * refuses an officer who may not decide and a decision older than the one 31 * already on file, and both of those are checked here. 32 */ 33 #include "test_common.h" 34 #include "exchange-database/account_history.h" 35 #include "exchange-database/insert_aml_decision.h" 36 #include "exchange-database/insert_aml_officer.h" 37 #include "exchange-database/iterate_aml_decisions.h" 38 #include "exchange-database/iterate_aml_history.h" 39 #include "exchange-database/iterate_aml_history_above_serial_id.h" 40 41 42 /** 43 * Account the checks decide about. 44 */ 45 static struct TDB_Account account; 46 47 48 /** 49 * Officer who takes the decisions. 50 */ 51 static struct TALER_AmlOfficerPublicKeyP officer_pub; 52 53 54 /** 55 * Build a timestamp from a number of seconds since the epoch. 56 * 57 * @param secs seconds since the epoch 58 * @return the timestamp 59 */ 60 static struct GNUNET_TIME_Timestamp 61 ts (uint64_t secs) 62 { 63 struct GNUNET_TIME_Absolute abs = { 64 .abs_value_us = secs * 1000LLU * 1000LLU 65 }; 66 67 return GNUNET_TIME_absolute_to_timestamp (abs); 68 } 69 70 71 /** 72 * Build a rule set naming @a name as its only rule. 73 * 74 * @param name name of the rule 75 * @return the rule set, to be freed with json_decref() 76 */ 77 static json_t * 78 make_rules (const char *name) 79 { 80 json_t *j; 81 82 j = GNUNET_JSON_PACK ( 83 GNUNET_JSON_pack_string ("rule_name", 84 name), 85 GNUNET_JSON_pack_array_steal ("rules", 86 json_array ())); 87 GNUNET_assert (NULL != j); 88 return j; 89 } 90 91 92 /** 93 * Closure for #history_cb() and #decision_cb(). 94 */ 95 struct HistoryContext 96 { 97 /** 98 * How many rows did the callback see? 99 */ 100 unsigned int total; 101 102 /** 103 * Stop after this many rows; 0 for no limit. 104 */ 105 unsigned int stop_after; 106 107 /** 108 * Justification of the last row seen, owned by this struct. 109 */ 110 char *justification; 111 112 /** 113 * Whether the last row asked for an investigation. 114 */ 115 bool to_investigate; 116 117 /** 118 * Whether the last row is the active decision. 119 */ 120 bool is_active; 121 122 /** 123 * Whether the last row named an officer. 124 */ 125 bool have_officer; 126 127 /** 128 * Whether the last decision exposed the officer's historical name. 129 */ 130 char *officer_name; 131 132 /** 133 * Whether the last decision linked submitted attributes. 134 */ 135 bool have_attributes; 136 137 /** 138 * Immediate measures of the last decision, owned by this struct. 139 */ 140 char *new_measures; 141 }; 142 143 144 /** 145 * Callback for #TALER_EXCHANGEDB_iterate_aml_history(). 146 * 147 * @param cls a `struct HistoryContext *` 148 * @param outcome_serial_id row of the outcome the decision produced 149 * @param decision_time when the decision was taken 150 * @param justification why it was taken 151 * @param new_measures measures requested immediately 152 * @param decider_pub officer who made the decision 153 * @param decider_name officer name at decision time 154 * @param kyc_attributes_rowid linked submitted attributes 155 * @param decider_pub which officer took it 156 * @param jproperties new account properties 157 * @param jnew_rules new account rules 158 * @param to_investigate whether staff should investigate 159 * @param is_active whether this is the active decision 160 */ 161 static void 162 history_cb (void *cls, 163 uint64_t outcome_serial_id, 164 struct GNUNET_TIME_Timestamp decision_time, 165 const char *justification, 166 const struct TALER_AmlOfficerPublicKeyP *decider_pub, 167 const json_t *jproperties, 168 const json_t *jnew_rules, 169 bool to_investigate, 170 bool is_active) 171 { 172 struct HistoryContext *ctx = cls; 173 174 (void) outcome_serial_id; 175 (void) decision_time; 176 (void) jproperties; 177 (void) jnew_rules; 178 ctx->total++; 179 GNUNET_free (ctx->justification); 180 ctx->justification = (NULL == justification) 181 ? NULL 182 : GNUNET_strdup (justification); 183 ctx->to_investigate = to_investigate; 184 ctx->is_active = is_active; 185 ctx->have_officer = (NULL != decider_pub); 186 } 187 188 189 /** 190 * Callback for #TALER_EXCHANGEDB_iterate_aml_decisions(). 191 * 192 * @param cls a `struct HistoryContext *` 193 * @param row_id row of the decision 194 * @param justification why it was taken 195 * @param h_payto account it is about 196 * @param decision_time when it was taken 197 * @param expiration_time when the rules expire 198 * @param jproperties new account properties 199 * @param to_investigate whether staff should investigate 200 * @param is_active whether this is the active decision 201 * @param is_wallet whether the account is a wallet 202 * @param payto the account's payto URI 203 * @param account_rules the account's rules 204 */ 205 static void 206 decision_cb (void *cls, 207 uint64_t row_id, 208 const char *justification, 209 const char *new_measures, 210 const struct TALER_AmlOfficerPublicKeyP *decider_pub, 211 const char *decider_name, 212 const uint64_t *kyc_attributes_rowid, 213 const struct TALER_NormalizedPaytoHashP *h_payto, 214 struct GNUNET_TIME_Timestamp decision_time, 215 struct GNUNET_TIME_Absolute expiration_time, 216 const json_t *jproperties, 217 bool to_investigate, 218 bool is_active, 219 bool is_wallet, 220 struct TALER_FullPayto payto, 221 const json_t *account_rules) 222 { 223 struct HistoryContext *ctx = cls; 224 225 (void) row_id; 226 (void) h_payto; 227 (void) decision_time; 228 (void) expiration_time; 229 (void) jproperties; 230 (void) is_wallet; 231 (void) payto; 232 (void) account_rules; 233 ctx->total++; 234 GNUNET_free (ctx->justification); 235 ctx->justification = (NULL == justification) 236 ? NULL 237 : GNUNET_strdup (justification); 238 GNUNET_free (ctx->new_measures); 239 ctx->new_measures = (NULL == new_measures) 240 ? NULL 241 : GNUNET_strdup (new_measures); 242 ctx->to_investigate = to_investigate; 243 ctx->is_active = is_active; 244 ctx->have_officer = (NULL != decider_pub); 245 GNUNET_free (ctx->officer_name); 246 ctx->officer_name = (NULL == decider_name) 247 ? NULL 248 : GNUNET_strdup (decider_name); 249 ctx->have_attributes = (NULL != kyc_attributes_rowid); 250 } 251 252 253 /** 254 * Callback for 255 * #TALER_EXCHANGEDB_iterate_aml_history_above_serial_id(). 256 * 257 * @param cls a `struct HistoryContext *` 258 * @param rowid row of the decision 259 * @param h_payto account it is about 260 * @param justification why it was taken 261 * @param decider_pub officer who took it 262 * @param decider_sig the officer's signature 263 * @param decision_time when it was taken 264 * @param jproperties new account properties 265 * @param jnew_rules new account rules 266 * @param new_measure_name measure to apply immediately 267 * @param to_investigate whether staff should investigate 268 * @param attributes_expiration when the attributes expire 269 * @param h_attributes hash of the attributes 270 * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop 271 */ 272 static enum GNUNET_GenericReturnValue 273 serial_cb (void *cls, 274 uint64_t rowid, 275 const struct TALER_NormalizedPaytoHashP *h_payto, 276 const char *justification, 277 const struct TALER_AmlOfficerPublicKeyP *decider_pub, 278 const struct TALER_AmlOfficerSignatureP *decider_sig, 279 struct GNUNET_TIME_Timestamp decision_time, 280 const json_t *jproperties, 281 const json_t *jnew_rules, 282 const char *new_measure_name, 283 bool to_investigate, 284 struct GNUNET_TIME_Timestamp attributes_expiration, 285 const struct GNUNET_HashCode *h_attributes) 286 { 287 struct HistoryContext *ctx = cls; 288 289 (void) rowid; 290 (void) h_payto; 291 (void) decider_sig; 292 (void) decision_time; 293 (void) jproperties; 294 (void) jnew_rules; 295 (void) new_measure_name; 296 (void) attributes_expiration; 297 (void) h_attributes; 298 ctx->total++; 299 GNUNET_free (ctx->justification); 300 ctx->justification = (NULL == justification) 301 ? NULL 302 : GNUNET_strdup (justification); 303 ctx->to_investigate = to_investigate; 304 ctx->have_officer = (NULL != decider_pub); 305 if ( (0 != ctx->stop_after) && 306 (ctx->total >= ctx->stop_after) ) 307 return GNUNET_SYSERR; 308 return GNUNET_OK; 309 } 310 311 312 /** 313 * Outcome of an AML decision. 314 */ 315 struct DecisionStatus 316 { 317 /** 318 * May the officer not decide right now? 319 */ 320 bool invalid_officer; 321 322 /** 323 * Is the account unknown? 324 */ 325 bool unknown_account; 326 327 /** 328 * Time of the decision that was already on file. 329 */ 330 struct GNUNET_TIME_Timestamp last_date; 331 332 /** 333 * Row of the measures the decision put in place. 334 */ 335 uint64_t legitimization_measure_serial_id; 336 337 /** 338 * Is the account a wallet? 339 */ 340 bool is_wallet; 341 }; 342 343 344 /** 345 * Take an AML decision about our account. 346 * 347 * @param pg the database context 348 * @param decider officer taking the decision, NULL for none 349 * @param seed seed for the officer's signature 350 * @param when when the decision is taken, in seconds since the epoch 351 * @param justification why 352 * @param new_measures measures to request immediately 353 * @param add_attributes whether to link submitted attributes 354 * @param to_investigate whether staff should investigate 355 * @param[out] st set to the outcome 356 * @return transaction status 357 */ 358 static enum GNUNET_DB_QueryStatus 359 decide (struct TALER_EXCHANGEDB_PostgresContext *pg, 360 const struct TALER_AmlOfficerPublicKeyP *decider, 361 uint32_t seed, 362 uint64_t when, 363 const char *justification, 364 const char *new_measures, 365 bool add_attributes, 366 bool to_investigate, 367 struct DecisionStatus *st) 368 { 369 struct TALER_AmlOfficerSignatureP decider_sig; 370 struct GNUNET_HashCode attributes_hash; 371 const char encrypted_attributes[] = "encrypted attributes"; 372 struct TALER_FullPayto null_payto = { NULL }; 373 json_t *new_rules = make_rules ("decided"); 374 /* the argument is declared `const char *[static 0]', so it must not 375 be NULL even though no event is to be triggered */ 376 const char *no_events[1] = { NULL }; 377 enum GNUNET_DB_QueryStatus qs; 378 379 TDB_fill (&decider_sig, 380 sizeof (decider_sig), 381 seed); 382 TDB_fill (&attributes_hash, 383 sizeof (attributes_hash), 384 seed); 385 memset (st, 386 0, 387 sizeof (*st)); 388 qs = TALER_EXCHANGEDB_insert_aml_decision ( 389 pg, 390 null_payto, 391 &account.h_normalized, 392 ts (when), 393 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS), 394 NULL, 395 new_rules, 396 to_investigate, 397 new_measures, 398 NULL, 399 justification, 400 decider, 401 (NULL == decider) ? NULL : &decider_sig, 402 0, 403 no_events, 404 add_attributes ? "test-form" : NULL, 405 add_attributes ? sizeof (encrypted_attributes) : 0, 406 add_attributes ? encrypted_attributes : NULL, 407 add_attributes ? &attributes_hash : NULL, 408 add_attributes ? ts (when + 3600) : GNUNET_TIME_UNIT_ZERO_TS, 409 &st->invalid_officer, 410 &st->unknown_account, 411 &st->last_date, 412 &st->legitimization_measure_serial_id, 413 &st->is_wallet); 414 json_decref (new_rules); 415 return qs; 416 } 417 418 419 /** 420 * Nothing is known while the table is empty. 421 * 422 * @param pg the database context 423 * @return 0 on success 424 */ 425 static int 426 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 427 { 428 struct HistoryContext ctx = { 0 }; 429 430 TDB_account (pg, 431 10, 432 &account); 433 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 434 TALER_EXCHANGEDB_iterate_aml_history (pg, 435 &account.h_normalized, 436 0, 437 10, 438 &history_cb, 439 &ctx)); 440 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 441 TALER_EXCHANGEDB_iterate_aml_history_above_serial_id ( 442 pg, 443 0, 444 &serial_cb, 445 &ctx)); 446 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 447 TALER_EXCHANGEDB_iterate_aml_decisions (pg, 448 &account.h_normalized, 449 TALER_EXCHANGE_YNA_ALL, 450 TALER_EXCHANGE_YNA_ALL, 451 0, 452 10, 453 &decision_cb, 454 &ctx)); 455 FAILIF (0 != ctx.total); 456 return 0; 457 } 458 459 460 /** 461 * An officer who is not on file may not decide. 462 * 463 * @param pg the database context 464 * @return 0 on success 465 */ 466 static int 467 check_invalid_officer (struct TALER_EXCHANGEDB_PostgresContext *pg) 468 { 469 struct DecisionStatus st; 470 471 TDB_FILL (officer_pub, 472 20); 473 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 474 decide (pg, 475 &officer_pub, 476 20, 477 1600000000, 478 "no such officer", 479 NULL, 480 false, 481 false, 482 &st)); 483 FAILIF (! st.invalid_officer); 484 FAILIF (0 != TDB_count (pg, 485 "FROM aml_history")); 486 return 0; 487 } 488 489 490 /** 491 * An appointed officer's decision is recorded and becomes the active 492 * outcome. 493 * 494 * @param pg the database context 495 * @return 0 on success 496 */ 497 static int 498 check_decide (struct TALER_EXCHANGEDB_PostgresContext *pg) 499 { 500 struct TALER_MasterSignatureP master_sig; 501 struct GNUNET_TIME_Timestamp previous_change; 502 struct DecisionStatus st; 503 struct HistoryContext ctx; 504 505 TDB_FILL (master_sig, 506 20); 507 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 508 TALER_EXCHANGEDB_insert_aml_officer (pg, 509 &officer_pub, 510 &master_sig, 511 "Alex Officer", 512 true, 513 false, 514 ts (1500000000), 515 &previous_change)); 516 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 517 decide (pg, 518 &officer_pub, 519 20, 520 1600000000, 521 "looks fine", 522 "review-a", 523 false, 524 false, 525 &st)); 526 FAILIF (st.invalid_officer); 527 FAILIF (st.unknown_account); 528 FAILIF (1 != TDB_count (pg, 529 "FROM aml_history")); 530 FAILIF (1 != TDB_count (pg, 531 "FROM legitimization_outcomes")); 532 533 memset (&ctx, 534 0, 535 sizeof (ctx)); 536 FAILIF (0 >= 537 TALER_EXCHANGEDB_iterate_aml_history (pg, 538 &account.h_normalized, 539 0, 540 10, 541 &history_cb, 542 &ctx)); 543 FAILIF_C (1 != ctx.total, 544 GNUNET_free (ctx.justification)); 545 FAILIF_C (0 != strcmp (ctx.justification, 546 "looks fine"), 547 GNUNET_free (ctx.justification)); 548 FAILIF_C (! ctx.is_active, 549 GNUNET_free (ctx.justification)); 550 FAILIF_C (! ctx.have_officer, 551 GNUNET_free (ctx.justification)); 552 GNUNET_free (ctx.justification); 553 return 0; 554 } 555 556 557 /** 558 * A decision older than the one on file is refused; a newer one 559 * supersedes it. 560 * 561 * @param pg the database context 562 * @return 0 on success 563 */ 564 static int 565 check_supersede (struct TALER_EXCHANGEDB_PostgresContext *pg) 566 { 567 struct DecisionStatus st; 568 struct HistoryContext ctx; 569 570 /* an older decision is refused (signalled by NO_RESULTS), and the 571 decision that is already on file is reported back */ 572 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 573 decide (pg, 574 &officer_pub, 575 21, 576 1500000000, 577 "too late", 578 NULL, 579 false, 580 false, 581 &st)); 582 FAILIF (GNUNET_TIME_timestamp_cmp (st.last_date, 583 !=, 584 ts (1600000000))); 585 FAILIF (1 != TDB_count (pg, 586 "FROM aml_history")); 587 588 /* a newer one is taken and deactivates the previous outcome */ 589 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 590 decide (pg, 591 &officer_pub, 592 22, 593 1600003600, 594 "investigate this", 595 "+review-a review-b", 596 true, 597 true, 598 &st)); 599 FAILIF (2 != TDB_count (pg, 600 "FROM aml_history")); 601 FAILIF (2 != TDB_count (pg, 602 "FROM legitimization_outcomes")); 603 FAILIF (1 != TDB_count (pg, 604 "FROM legitimization_outcomes" 605 " WHERE is_active")); 606 607 memset (&ctx, 608 0, 609 sizeof (ctx)); 610 FAILIF (0 >= 611 TALER_EXCHANGEDB_iterate_aml_history (pg, 612 &account.h_normalized, 613 0, 614 10, 615 &history_cb, 616 &ctx)); 617 FAILIF_C (2 != ctx.total, 618 GNUNET_free (ctx.justification)); 619 GNUNET_free (ctx.justification); 620 return 0; 621 } 622 623 624 /** 625 * The AML officer's view filters by investigation and activity. 626 * 627 * @param pg the database context 628 * @return 0 on success 629 */ 630 static int 631 check_decisions (struct TALER_EXCHANGEDB_PostgresContext *pg) 632 { 633 struct TALER_MasterSignatureP master_sig; 634 struct GNUNET_TIME_Timestamp previous_change; 635 struct HistoryContext ctx; 636 637 /* A later rename must not rewrite the name shown on older decisions. */ 638 TDB_FILL (master_sig, 639 30); 640 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 641 TALER_EXCHANGEDB_insert_aml_officer (pg, 642 &officer_pub, 643 &master_sig, 644 "Alex Renamed", 645 true, 646 false, 647 ts (1700000000), 648 &previous_change)); 649 650 memset (&ctx, 651 0, 652 sizeof (ctx)); 653 FAILIF (0 >= 654 TALER_EXCHANGEDB_iterate_aml_decisions (pg, 655 &account.h_normalized, 656 TALER_EXCHANGE_YNA_ALL, 657 TALER_EXCHANGE_YNA_ALL, 658 0, 659 10, 660 &decision_cb, 661 &ctx)); 662 FAILIF_C (2 != ctx.total, 663 GNUNET_free (ctx.justification)); 664 FAILIF_C (NULL == ctx.new_measures, 665 GNUNET_free (ctx.justification)); 666 FAILIF_C (0 != strcmp (ctx.new_measures, 667 "+review-a review-b"), 668 GNUNET_free (ctx.justification)); 669 FAILIF_C (NULL == ctx.officer_name, 670 GNUNET_free (ctx.justification)); 671 FAILIF_C (0 != strcmp (ctx.officer_name, 672 "Alex Officer"), 673 GNUNET_free (ctx.justification)); 674 FAILIF_C (! ctx.have_officer, 675 GNUNET_free (ctx.justification)); 676 FAILIF_C (! ctx.have_attributes, 677 GNUNET_free (ctx.justification)); 678 GNUNET_free (ctx.justification); 679 GNUNET_free (ctx.new_measures); 680 GNUNET_free (ctx.officer_name); 681 682 /* only the newer decision asked for an investigation */ 683 memset (&ctx, 684 0, 685 sizeof (ctx)); 686 FAILIF (0 >= 687 TALER_EXCHANGEDB_iterate_aml_decisions (pg, 688 &account.h_normalized, 689 TALER_EXCHANGE_YNA_YES, 690 TALER_EXCHANGE_YNA_ALL, 691 0, 692 10, 693 &decision_cb, 694 &ctx)); 695 FAILIF_C (1 != ctx.total, 696 GNUNET_free (ctx.justification)); 697 FAILIF_C (0 != strcmp (ctx.justification, 698 "investigate this"), 699 GNUNET_free (ctx.justification)); 700 GNUNET_free (ctx.justification); 701 GNUNET_free (ctx.new_measures); 702 GNUNET_free (ctx.officer_name); 703 704 /* and only one of them is the active outcome */ 705 memset (&ctx, 706 0, 707 sizeof (ctx)); 708 FAILIF (0 >= 709 TALER_EXCHANGEDB_iterate_aml_decisions (pg, 710 &account.h_normalized, 711 TALER_EXCHANGE_YNA_ALL, 712 TALER_EXCHANGE_YNA_YES, 713 0, 714 10, 715 &decision_cb, 716 &ctx)); 717 FAILIF_C (1 != ctx.total, 718 GNUNET_free (ctx.justification)); 719 FAILIF_C (! ctx.is_active, 720 GNUNET_free (ctx.justification)); 721 GNUNET_free (ctx.justification); 722 GNUNET_free (ctx.new_measures); 723 GNUNET_free (ctx.officer_name); 724 725 /* an account nobody decided about has nothing */ 726 { 727 struct TALER_NormalizedPaytoHashP other; 728 729 TDB_FILL (other, 730 98); 731 memset (&ctx, 732 0, 733 sizeof (ctx)); 734 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 735 TALER_EXCHANGEDB_iterate_aml_decisions (pg, 736 &other, 737 TALER_EXCHANGE_YNA_ALL, 738 TALER_EXCHANGE_YNA_ALL, 739 0, 740 10, 741 &decision_cb, 742 &ctx)); 743 FAILIF (0 != ctx.total); 744 } 745 return 0; 746 } 747 748 749 /** 750 * The auditor's view walks the decisions by serial and honours an 751 * aborting callback. 752 * 753 * @param pg the database context 754 * @return 0 on success 755 */ 756 static int 757 check_serial (struct TALER_EXCHANGEDB_PostgresContext *pg) 758 { 759 struct HistoryContext ctx; 760 761 memset (&ctx, 762 0, 763 sizeof (ctx)); 764 FAILIF (0 >= 765 TALER_EXCHANGEDB_iterate_aml_history_above_serial_id ( 766 pg, 767 0, 768 &serial_cb, 769 &ctx)); 770 FAILIF_C (2 != ctx.total, 771 GNUNET_free (ctx.justification)); 772 FAILIF_C (! ctx.have_officer, 773 GNUNET_free (ctx.justification)); 774 GNUNET_free (ctx.justification); 775 776 memset (&ctx, 777 0, 778 sizeof (ctx)); 779 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 780 TALER_EXCHANGEDB_iterate_aml_history_above_serial_id ( 781 pg, 782 1000, 783 &serial_cb, 784 &ctx)); 785 FAILIF (0 != ctx.total); 786 787 memset (&ctx, 788 0, 789 sizeof (ctx)); 790 ctx.stop_after = 1; 791 FAILIF (0 >= 792 TALER_EXCHANGEDB_iterate_aml_history_above_serial_id ( 793 pg, 794 0, 795 &serial_cb, 796 &ctx)); 797 FAILIF_C (1 != ctx.total, 798 GNUNET_free (ctx.justification)); 799 GNUNET_free (ctx.justification); 800 return 0; 801 } 802 803 804 /** 805 * The history builder renders the account's AML history as JSON. 806 * 807 * @param pg the database context 808 * @return 0 on success 809 */ 810 static int 811 check_history_builder (struct TALER_EXCHANGEDB_PostgresContext *pg) 812 { 813 struct TALER_AttributeEncryptionKeyP attribute_key; 814 struct TALER_EXCHANGEDB_HistoryBuilderContext hbc = { 815 .account = &account.h_normalized, 816 .pg = pg, 817 .attribute_key = &attribute_key, 818 .is_wallet = false 819 }; 820 json_t *j; 821 822 TDB_FILL (attribute_key, 823 1); 824 j = TALER_EXCHANGEDB_aml_history_builder (&hbc); 825 FAILIF (NULL == j); 826 FAILIF_C (! json_is_array (j), 827 json_decref (j)); 828 FAILIF_C (2 != json_array_size (j), 829 json_decref (j)); 830 json_decref (j); 831 return 0; 832 } 833 834 835 /** 836 * Automated outcomes have no AML officer provenance. 837 * 838 * @param pg the database context 839 * @return 0 on success 840 */ 841 static int 842 check_automated_outcome (struct TALER_EXCHANGEDB_PostgresContext *pg) 843 { 844 struct DecisionStatus st; 845 struct HistoryContext ctx = { 0 }; 846 847 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 848 decide (pg, 849 NULL, 850 40, 851 1800000000, 852 NULL, 853 NULL, 854 false, 855 false, 856 &st)); 857 FAILIF (0 >= 858 TALER_EXCHANGEDB_iterate_aml_decisions (pg, 859 &account.h_normalized, 860 TALER_EXCHANGE_YNA_ALL, 861 TALER_EXCHANGE_YNA_YES, 862 0, 863 10, 864 &decision_cb, 865 &ctx)); 866 FAILIF_C (1 != ctx.total, 867 GNUNET_free (ctx.justification)); 868 FAILIF_C (ctx.have_officer, 869 GNUNET_free (ctx.justification)); 870 FAILIF_C (NULL != ctx.officer_name, 871 GNUNET_free (ctx.justification)); 872 GNUNET_free (ctx.justification); 873 GNUNET_free (ctx.new_measures); 874 GNUNET_free (ctx.officer_name); 875 return 0; 876 } 877 878 879 /** 880 * The checks to run, in order. 881 */ 882 static const struct TDB_Test tests[] = { 883 { "aml-history-empty", 884 &check_empty }, 885 { "aml-history-invalid-officer", 886 &check_invalid_officer }, 887 { "aml-history-decide", 888 &check_decide }, 889 { "aml-history-supersede", 890 &check_supersede }, 891 { "aml-history-decisions", 892 &check_decisions }, 893 { "aml-history-serial", 894 &check_serial }, 895 { "aml-history-history-builder", 896 &check_history_builder }, 897 { "aml-history-automated-outcome", 898 &check_automated_outcome }, 899 { NULL, NULL } 900 }; 901 902 903 int 904 main (int argc, 905 char *const *argv) 906 { 907 int ret; 908 909 ret = TDB_main (argc, 910 argv, 911 "test-aml-history", 912 "Tests for the exchangedb `aml_history' table", 913 tests); 914 TDB_account_free (&account); 915 return ret; 916 } 917 918 919 /* end of test_aml_history.c */