exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 */