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 (22962B)


      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 
    129 /**
    130  * Callback for #TALER_EXCHANGEDB_iterate_aml_history().
    131  *
    132  * @param cls a `struct HistoryContext *`
    133  * @param outcome_serial_id row of the outcome the decision produced
    134  * @param decision_time when the decision was taken
    135  * @param justification why it was taken
    136  * @param decider_pub which officer took it
    137  * @param jproperties new account properties
    138  * @param jnew_rules new account rules
    139  * @param to_investigate whether staff should investigate
    140  * @param is_active whether this is the active decision
    141  */
    142 static void
    143 history_cb (void *cls,
    144             uint64_t outcome_serial_id,
    145             struct GNUNET_TIME_Timestamp decision_time,
    146             const char *justification,
    147             const struct TALER_AmlOfficerPublicKeyP *decider_pub,
    148             const json_t *jproperties,
    149             const json_t *jnew_rules,
    150             bool to_investigate,
    151             bool is_active)
    152 {
    153   struct HistoryContext *ctx = cls;
    154 
    155   (void) outcome_serial_id;
    156   (void) decision_time;
    157   (void) jproperties;
    158   (void) jnew_rules;
    159   ctx->total++;
    160   GNUNET_free (ctx->justification);
    161   ctx->justification = (NULL == justification)
    162     ? NULL
    163     : GNUNET_strdup (justification);
    164   ctx->to_investigate = to_investigate;
    165   ctx->is_active = is_active;
    166   ctx->have_officer = (NULL != decider_pub);
    167 }
    168 
    169 
    170 /**
    171  * Callback for #TALER_EXCHANGEDB_iterate_aml_decisions().
    172  *
    173  * @param cls a `struct HistoryContext *`
    174  * @param row_id row of the decision
    175  * @param justification why it was taken
    176  * @param h_payto account it is about
    177  * @param decision_time when it was taken
    178  * @param expiration_time when the rules expire
    179  * @param jproperties new account properties
    180  * @param to_investigate whether staff should investigate
    181  * @param is_active whether this is the active decision
    182  * @param is_wallet whether the account is a wallet
    183  * @param payto the account's payto URI
    184  * @param account_rules the account's rules
    185  */
    186 static void
    187 decision_cb (void *cls,
    188              uint64_t row_id,
    189              const char *justification,
    190              const struct TALER_NormalizedPaytoHashP *h_payto,
    191              struct GNUNET_TIME_Timestamp decision_time,
    192              struct GNUNET_TIME_Absolute expiration_time,
    193              const json_t *jproperties,
    194              bool to_investigate,
    195              bool is_active,
    196              bool is_wallet,
    197              struct TALER_FullPayto payto,
    198              const json_t *account_rules)
    199 {
    200   struct HistoryContext *ctx = cls;
    201 
    202   (void) row_id;
    203   (void) h_payto;
    204   (void) decision_time;
    205   (void) expiration_time;
    206   (void) jproperties;
    207   (void) is_wallet;
    208   (void) payto;
    209   (void) account_rules;
    210   ctx->total++;
    211   GNUNET_free (ctx->justification);
    212   ctx->justification = (NULL == justification)
    213     ? NULL
    214     : GNUNET_strdup (justification);
    215   ctx->to_investigate = to_investigate;
    216   ctx->is_active = is_active;
    217 }
    218 
    219 
    220 /**
    221  * Callback for
    222  * #TALER_EXCHANGEDB_iterate_aml_history_above_serial_id().
    223  *
    224  * @param cls a `struct HistoryContext *`
    225  * @param rowid row of the decision
    226  * @param h_payto account it is about
    227  * @param justification why it was taken
    228  * @param decider_pub officer who took it
    229  * @param decider_sig the officer's signature
    230  * @param decision_time when it was taken
    231  * @param jproperties new account properties
    232  * @param jnew_rules new account rules
    233  * @param new_measure_name measure to apply immediately
    234  * @param to_investigate whether staff should investigate
    235  * @param attributes_expiration when the attributes expire
    236  * @param h_attributes hash of the attributes
    237  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
    238  */
    239 static enum GNUNET_GenericReturnValue
    240 serial_cb (void *cls,
    241            uint64_t rowid,
    242            const struct TALER_NormalizedPaytoHashP *h_payto,
    243            const char *justification,
    244            const struct TALER_AmlOfficerPublicKeyP *decider_pub,
    245            const struct TALER_AmlOfficerSignatureP *decider_sig,
    246            struct GNUNET_TIME_Timestamp decision_time,
    247            const json_t *jproperties,
    248            const json_t *jnew_rules,
    249            const char *new_measure_name,
    250            bool to_investigate,
    251            struct GNUNET_TIME_Timestamp attributes_expiration,
    252            const struct GNUNET_HashCode *h_attributes)
    253 {
    254   struct HistoryContext *ctx = cls;
    255 
    256   (void) rowid;
    257   (void) h_payto;
    258   (void) decider_sig;
    259   (void) decision_time;
    260   (void) jproperties;
    261   (void) jnew_rules;
    262   (void) new_measure_name;
    263   (void) attributes_expiration;
    264   (void) h_attributes;
    265   ctx->total++;
    266   GNUNET_free (ctx->justification);
    267   ctx->justification = (NULL == justification)
    268     ? NULL
    269     : GNUNET_strdup (justification);
    270   ctx->to_investigate = to_investigate;
    271   ctx->have_officer = (NULL != decider_pub);
    272   if ( (0 != ctx->stop_after) &&
    273        (ctx->total >= ctx->stop_after) )
    274     return GNUNET_SYSERR;
    275   return GNUNET_OK;
    276 }
    277 
    278 
    279 /**
    280  * Outcome of an AML decision.
    281  */
    282 struct DecisionStatus
    283 {
    284   /**
    285    * May the officer not decide right now?
    286    */
    287   bool invalid_officer;
    288 
    289   /**
    290    * Is the account unknown?
    291    */
    292   bool unknown_account;
    293 
    294   /**
    295    * Time of the decision that was already on file.
    296    */
    297   struct GNUNET_TIME_Timestamp last_date;
    298 
    299   /**
    300    * Row of the measures the decision put in place.
    301    */
    302   uint64_t legitimization_measure_serial_id;
    303 
    304   /**
    305    * Is the account a wallet?
    306    */
    307   bool is_wallet;
    308 };
    309 
    310 
    311 /**
    312  * Take an AML decision about our account.
    313  *
    314  * @param pg the database context
    315  * @param decider officer taking the decision, NULL for none
    316  * @param seed seed for the officer's signature
    317  * @param when when the decision is taken, in seconds since the epoch
    318  * @param justification why
    319  * @param to_investigate whether staff should investigate
    320  * @param[out] st set to the outcome
    321  * @return transaction status
    322  */
    323 static enum GNUNET_DB_QueryStatus
    324 decide (struct TALER_EXCHANGEDB_PostgresContext *pg,
    325         const struct TALER_AmlOfficerPublicKeyP *decider,
    326         uint32_t seed,
    327         uint64_t when,
    328         const char *justification,
    329         bool to_investigate,
    330         struct DecisionStatus *st)
    331 {
    332   struct TALER_AmlOfficerSignatureP decider_sig;
    333   struct TALER_FullPayto null_payto = { NULL };
    334   json_t *new_rules = make_rules ("decided");
    335   /* the argument is declared `const char *[static 0]', so it must not
    336      be NULL even though no event is to be triggered */
    337   const char *no_events[1] = { NULL };
    338   enum GNUNET_DB_QueryStatus qs;
    339 
    340   TDB_fill (&decider_sig,
    341             sizeof (decider_sig),
    342             seed);
    343   memset (st,
    344           0,
    345           sizeof (*st));
    346   qs = TALER_EXCHANGEDB_insert_aml_decision (
    347     pg,
    348     null_payto,
    349     &account.h_normalized,
    350     ts (when),
    351     GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
    352     NULL,
    353     new_rules,
    354     to_investigate,
    355     NULL,
    356     NULL,
    357     justification,
    358     decider,
    359     (NULL == decider) ? NULL : &decider_sig,
    360     0,
    361     no_events,
    362     NULL,
    363     0,
    364     NULL,
    365     NULL,
    366     GNUNET_TIME_UNIT_ZERO_TS,
    367     &st->invalid_officer,
    368     &st->unknown_account,
    369     &st->last_date,
    370     &st->legitimization_measure_serial_id,
    371     &st->is_wallet);
    372   json_decref (new_rules);
    373   return qs;
    374 }
    375 
    376 
    377 /**
    378  * Nothing is known while the table is empty.
    379  *
    380  * @param pg the database context
    381  * @return 0 on success
    382  */
    383 static int
    384 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    385 {
    386   struct HistoryContext ctx = { 0 };
    387 
    388   TDB_account (pg,
    389                10,
    390                &account);
    391   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    392           TALER_EXCHANGEDB_iterate_aml_history (pg,
    393                                                 &account.h_normalized,
    394                                                 0,
    395                                                 10,
    396                                                 &history_cb,
    397                                                 &ctx));
    398   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    399           TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
    400             pg,
    401             0,
    402             &serial_cb,
    403             &ctx));
    404   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    405           TALER_EXCHANGEDB_iterate_aml_decisions (pg,
    406                                                   &account.h_normalized,
    407                                                   TALER_EXCHANGE_YNA_ALL,
    408                                                   TALER_EXCHANGE_YNA_ALL,
    409                                                   0,
    410                                                   10,
    411                                                   &decision_cb,
    412                                                   &ctx));
    413   FAILIF (0 != ctx.total);
    414   return 0;
    415 }
    416 
    417 
    418 /**
    419  * An officer who is not on file may not decide.
    420  *
    421  * @param pg the database context
    422  * @return 0 on success
    423  */
    424 static int
    425 check_invalid_officer (struct TALER_EXCHANGEDB_PostgresContext *pg)
    426 {
    427   struct DecisionStatus st;
    428 
    429   TDB_FILL (officer_pub,
    430             20);
    431   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    432           decide (pg,
    433                   &officer_pub,
    434                   20,
    435                   1600000000,
    436                   "no such officer",
    437                   false,
    438                   &st));
    439   FAILIF (! st.invalid_officer);
    440   FAILIF (0 != TDB_count (pg,
    441                           "FROM aml_history"));
    442   return 0;
    443 }
    444 
    445 
    446 /**
    447  * An appointed officer's decision is recorded and becomes the active
    448  * outcome.
    449  *
    450  * @param pg the database context
    451  * @return 0 on success
    452  */
    453 static int
    454 check_decide (struct TALER_EXCHANGEDB_PostgresContext *pg)
    455 {
    456   struct TALER_MasterSignatureP master_sig;
    457   struct GNUNET_TIME_Timestamp previous_change;
    458   struct DecisionStatus st;
    459   struct HistoryContext ctx;
    460 
    461   TDB_FILL (master_sig,
    462             20);
    463   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    464           TALER_EXCHANGEDB_insert_aml_officer (pg,
    465                                                &officer_pub,
    466                                                &master_sig,
    467                                                "Alex Officer",
    468                                                true,
    469                                                false,
    470                                                ts (1500000000),
    471                                                &previous_change));
    472   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    473           decide (pg,
    474                   &officer_pub,
    475                   20,
    476                   1600000000,
    477                   "looks fine",
    478                   false,
    479                   &st));
    480   FAILIF (st.invalid_officer);
    481   FAILIF (st.unknown_account);
    482   FAILIF (1 != TDB_count (pg,
    483                           "FROM aml_history"));
    484   FAILIF (1 != TDB_count (pg,
    485                           "FROM legitimization_outcomes"));
    486 
    487   memset (&ctx,
    488           0,
    489           sizeof (ctx));
    490   FAILIF (0 >=
    491           TALER_EXCHANGEDB_iterate_aml_history (pg,
    492                                                 &account.h_normalized,
    493                                                 0,
    494                                                 10,
    495                                                 &history_cb,
    496                                                 &ctx));
    497   FAILIF_C (1 != ctx.total,
    498             GNUNET_free (ctx.justification));
    499   FAILIF_C (0 != strcmp (ctx.justification,
    500                          "looks fine"),
    501             GNUNET_free (ctx.justification));
    502   FAILIF_C (! ctx.is_active,
    503             GNUNET_free (ctx.justification));
    504   FAILIF_C (! ctx.have_officer,
    505             GNUNET_free (ctx.justification));
    506   GNUNET_free (ctx.justification);
    507   return 0;
    508 }
    509 
    510 
    511 /**
    512  * A decision older than the one on file is refused; a newer one
    513  * supersedes it.
    514  *
    515  * @param pg the database context
    516  * @return 0 on success
    517  */
    518 static int
    519 check_supersede (struct TALER_EXCHANGEDB_PostgresContext *pg)
    520 {
    521   struct DecisionStatus st;
    522   struct HistoryContext ctx;
    523 
    524   /* an older decision is refused (signalled by NO_RESULTS), and the
    525      decision that is already on file is reported back */
    526   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    527           decide (pg,
    528                   &officer_pub,
    529                   21,
    530                   1500000000,
    531                   "too late",
    532                   false,
    533                   &st));
    534   FAILIF (GNUNET_TIME_timestamp_cmp (st.last_date,
    535                                      !=,
    536                                      ts (1600000000)));
    537   FAILIF (1 != TDB_count (pg,
    538                           "FROM aml_history"));
    539 
    540   /* a newer one is taken and deactivates the previous outcome */
    541   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    542           decide (pg,
    543                   &officer_pub,
    544                   22,
    545                   1600003600,
    546                   "investigate this",
    547                   true,
    548                   &st));
    549   FAILIF (2 != TDB_count (pg,
    550                           "FROM aml_history"));
    551   FAILIF (2 != TDB_count (pg,
    552                           "FROM legitimization_outcomes"));
    553   FAILIF (1 != TDB_count (pg,
    554                           "FROM legitimization_outcomes"
    555                           " WHERE is_active"));
    556 
    557   memset (&ctx,
    558           0,
    559           sizeof (ctx));
    560   FAILIF (0 >=
    561           TALER_EXCHANGEDB_iterate_aml_history (pg,
    562                                                 &account.h_normalized,
    563                                                 0,
    564                                                 10,
    565                                                 &history_cb,
    566                                                 &ctx));
    567   FAILIF_C (2 != ctx.total,
    568             GNUNET_free (ctx.justification));
    569   GNUNET_free (ctx.justification);
    570   return 0;
    571 }
    572 
    573 
    574 /**
    575  * The AML officer's view filters by investigation and activity.
    576  *
    577  * @param pg the database context
    578  * @return 0 on success
    579  */
    580 static int
    581 check_decisions (struct TALER_EXCHANGEDB_PostgresContext *pg)
    582 {
    583   struct HistoryContext ctx;
    584 
    585   memset (&ctx,
    586           0,
    587           sizeof (ctx));
    588   FAILIF (0 >=
    589           TALER_EXCHANGEDB_iterate_aml_decisions (pg,
    590                                                   &account.h_normalized,
    591                                                   TALER_EXCHANGE_YNA_ALL,
    592                                                   TALER_EXCHANGE_YNA_ALL,
    593                                                   0,
    594                                                   10,
    595                                                   &decision_cb,
    596                                                   &ctx));
    597   FAILIF_C (2 != ctx.total,
    598             GNUNET_free (ctx.justification));
    599   GNUNET_free (ctx.justification);
    600 
    601   /* only the newer decision asked for an investigation */
    602   memset (&ctx,
    603           0,
    604           sizeof (ctx));
    605   FAILIF (0 >=
    606           TALER_EXCHANGEDB_iterate_aml_decisions (pg,
    607                                                   &account.h_normalized,
    608                                                   TALER_EXCHANGE_YNA_YES,
    609                                                   TALER_EXCHANGE_YNA_ALL,
    610                                                   0,
    611                                                   10,
    612                                                   &decision_cb,
    613                                                   &ctx));
    614   FAILIF_C (1 != ctx.total,
    615             GNUNET_free (ctx.justification));
    616   FAILIF_C (0 != strcmp (ctx.justification,
    617                          "investigate this"),
    618             GNUNET_free (ctx.justification));
    619   GNUNET_free (ctx.justification);
    620 
    621   /* and only one of them is the active outcome */
    622   memset (&ctx,
    623           0,
    624           sizeof (ctx));
    625   FAILIF (0 >=
    626           TALER_EXCHANGEDB_iterate_aml_decisions (pg,
    627                                                   &account.h_normalized,
    628                                                   TALER_EXCHANGE_YNA_ALL,
    629                                                   TALER_EXCHANGE_YNA_YES,
    630                                                   0,
    631                                                   10,
    632                                                   &decision_cb,
    633                                                   &ctx));
    634   FAILIF_C (1 != ctx.total,
    635             GNUNET_free (ctx.justification));
    636   FAILIF_C (! ctx.is_active,
    637             GNUNET_free (ctx.justification));
    638   GNUNET_free (ctx.justification);
    639 
    640   /* an account nobody decided about has nothing */
    641   {
    642     struct TALER_NormalizedPaytoHashP other;
    643 
    644     TDB_FILL (other,
    645               98);
    646     memset (&ctx,
    647             0,
    648             sizeof (ctx));
    649     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    650             TALER_EXCHANGEDB_iterate_aml_decisions (pg,
    651                                                     &other,
    652                                                     TALER_EXCHANGE_YNA_ALL,
    653                                                     TALER_EXCHANGE_YNA_ALL,
    654                                                     0,
    655                                                     10,
    656                                                     &decision_cb,
    657                                                     &ctx));
    658     FAILIF (0 != ctx.total);
    659   }
    660   return 0;
    661 }
    662 
    663 
    664 /**
    665  * The auditor's view walks the decisions by serial and honours an
    666  * aborting callback.
    667  *
    668  * @param pg the database context
    669  * @return 0 on success
    670  */
    671 static int
    672 check_serial (struct TALER_EXCHANGEDB_PostgresContext *pg)
    673 {
    674   struct HistoryContext ctx;
    675 
    676   memset (&ctx,
    677           0,
    678           sizeof (ctx));
    679   FAILIF (0 >=
    680           TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
    681             pg,
    682             0,
    683             &serial_cb,
    684             &ctx));
    685   FAILIF_C (2 != ctx.total,
    686             GNUNET_free (ctx.justification));
    687   FAILIF_C (! ctx.have_officer,
    688             GNUNET_free (ctx.justification));
    689   GNUNET_free (ctx.justification);
    690 
    691   memset (&ctx,
    692           0,
    693           sizeof (ctx));
    694   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    695           TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
    696             pg,
    697             1000,
    698             &serial_cb,
    699             &ctx));
    700   FAILIF (0 != ctx.total);
    701 
    702   memset (&ctx,
    703           0,
    704           sizeof (ctx));
    705   ctx.stop_after = 1;
    706   FAILIF (0 >=
    707           TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
    708             pg,
    709             0,
    710             &serial_cb,
    711             &ctx));
    712   FAILIF_C (1 != ctx.total,
    713             GNUNET_free (ctx.justification));
    714   GNUNET_free (ctx.justification);
    715   return 0;
    716 }
    717 
    718 
    719 /**
    720  * The history builder renders the account's AML history as JSON.
    721  *
    722  * @param pg the database context
    723  * @return 0 on success
    724  */
    725 static int
    726 check_history_builder (struct TALER_EXCHANGEDB_PostgresContext *pg)
    727 {
    728   struct TALER_AttributeEncryptionKeyP attribute_key;
    729   struct TALER_EXCHANGEDB_HistoryBuilderContext hbc = {
    730     .account = &account.h_normalized,
    731     .pg = pg,
    732     .attribute_key = &attribute_key,
    733     .is_wallet = false
    734   };
    735   json_t *j;
    736 
    737   TDB_FILL (attribute_key,
    738             1);
    739   j = TALER_EXCHANGEDB_aml_history_builder (&hbc);
    740   FAILIF (NULL == j);
    741   FAILIF_C (! json_is_array (j),
    742             json_decref (j));
    743   FAILIF_C (2 != json_array_size (j),
    744             json_decref (j));
    745   json_decref (j);
    746   return 0;
    747 }
    748 
    749 
    750 /**
    751  * The checks to run, in order.
    752  */
    753 static const struct TDB_Test tests[] = {
    754   { "aml-history-empty",
    755     &check_empty },
    756   { "aml-history-invalid-officer",
    757     &check_invalid_officer },
    758   { "aml-history-decide",
    759     &check_decide },
    760   { "aml-history-supersede",
    761     &check_supersede },
    762   { "aml-history-decisions",
    763     &check_decisions },
    764   { "aml-history-serial",
    765     &check_serial },
    766   { "aml-history-history-builder",
    767     &check_history_builder },
    768   { NULL, NULL }
    769 };
    770 
    771 
    772 int
    773 main (int argc,
    774       char *const *argv)
    775 {
    776   int ret;
    777 
    778   ret = TDB_main (argc,
    779                   argv,
    780                   "test-aml-history",
    781                   "Tests for the exchangedb `aml_history' table",
    782                   tests);
    783   TDB_account_free (&account);
    784   return ret;
    785 }
    786 
    787 
    788 /* end of test_aml_history.c */