LCOV - code coverage report
Current view: top level - exchangedb - test_aml_history.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 175 175
Test Date: 2026-09-11 18:55:36 Functions: 100.0 % 15 15

            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_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            9 : ts (uint64_t secs)
      62              : {
      63            9 :   struct GNUNET_TIME_Absolute abs = {
      64            9 :     .abs_value_us = secs * 1000LLU * 1000LLU
      65              :   };
      66              : 
      67            9 :   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            5 : make_rules (const char *name)
      79              : {
      80              :   json_t *j;
      81              : 
      82            5 :   j = GNUNET_JSON_PACK (
      83              :     GNUNET_JSON_pack_string ("rule_name",
      84              :                              name),
      85              :     GNUNET_JSON_pack_array_steal ("rules",
      86              :                                   json_array ()));
      87            5 :   GNUNET_assert (NULL != j);
      88            5 :   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            3 : 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            3 :   struct HistoryContext *ctx = cls;
     173              : 
     174              :   (void) outcome_serial_id;
     175              :   (void) decision_time;
     176              :   (void) jproperties;
     177              :   (void) jnew_rules;
     178            3 :   ctx->total++;
     179            3 :   GNUNET_free (ctx->justification);
     180            3 :   ctx->justification = (NULL == justification)
     181              :     ? NULL
     182            3 :     : GNUNET_strdup (justification);
     183            3 :   ctx->to_investigate = to_investigate;
     184            3 :   ctx->is_active = is_active;
     185            3 :   ctx->have_officer = (NULL != decider_pub);
     186            3 : }
     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            5 : 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            5 :   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            5 :   ctx->total++;
     234            5 :   GNUNET_free (ctx->justification);
     235            5 :   ctx->justification = (NULL == justification)
     236              :     ? NULL
     237            5 :     : GNUNET_strdup (justification);
     238            5 :   GNUNET_free (ctx->new_measures);
     239            5 :   ctx->new_measures = (NULL == new_measures)
     240              :     ? NULL
     241            5 :     : GNUNET_strdup (new_measures);
     242            5 :   ctx->to_investigate = to_investigate;
     243            5 :   ctx->is_active = is_active;
     244            5 :   ctx->have_officer = (NULL != decider_pub);
     245            5 :   GNUNET_free (ctx->officer_name);
     246            5 :   ctx->officer_name = (NULL == decider_name)
     247              :     ? NULL
     248            5 :     : GNUNET_strdup (decider_name);
     249            5 :   ctx->have_attributes = (NULL != kyc_attributes_rowid);
     250            5 : }
     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            3 : 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            3 :   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            3 :   ctx->total++;
     299            3 :   GNUNET_free (ctx->justification);
     300            3 :   ctx->justification = (NULL == justification)
     301              :     ? NULL
     302            3 :     : GNUNET_strdup (justification);
     303            3 :   ctx->to_investigate = to_investigate;
     304            3 :   ctx->have_officer = (NULL != decider_pub);
     305            3 :   if ( (0 != ctx->stop_after) &&
     306            1 :        (ctx->total >= ctx->stop_after) )
     307            1 :     return GNUNET_SYSERR;
     308            2 :   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            5 : 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            5 :   const char encrypted_attributes[] = "encrypted attributes";
     372            5 :   struct TALER_FullPayto null_payto = { NULL };
     373            5 :   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            5 :   const char *no_events[1] = { NULL };
     377              :   enum GNUNET_DB_QueryStatus qs;
     378              : 
     379            5 :   TDB_fill (&decider_sig,
     380              :             sizeof (decider_sig),
     381              :             seed);
     382            5 :   TDB_fill (&attributes_hash,
     383              :             sizeof (attributes_hash),
     384              :             seed);
     385            5 :   memset (st,
     386              :           0,
     387              :           sizeof (*st));
     388            6 :   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            1 :     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            5 :   json_decref (new_rules);
     415            5 :   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            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     427              : {
     428            1 :   struct HistoryContext ctx = { 0 };
     429              : 
     430            1 :   TDB_account (pg,
     431              :                10,
     432              :                &account);
     433            1 :   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            1 :   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            1 :   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            1 :   FAILIF (0 != ctx.total);
     456            1 :   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            1 : check_invalid_officer (struct TALER_EXCHANGEDB_PostgresContext *pg)
     468              : {
     469              :   struct DecisionStatus st;
     470              : 
     471            1 :   TDB_FILL (officer_pub,
     472              :             20);
     473            1 :   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            1 :   FAILIF (! st.invalid_officer);
     484            1 :   FAILIF (0 != TDB_count (pg,
     485              :                           "FROM aml_history"));
     486            1 :   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            1 : 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            1 :   TDB_FILL (master_sig,
     506              :             20);
     507            1 :   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            1 :   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            1 :   FAILIF (st.invalid_officer);
     527            1 :   FAILIF (st.unknown_account);
     528            1 :   FAILIF (1 != TDB_count (pg,
     529              :                           "FROM aml_history"));
     530            1 :   FAILIF (1 != TDB_count (pg,
     531              :                           "FROM legitimization_outcomes"));
     532              : 
     533            1 :   memset (&ctx,
     534              :           0,
     535              :           sizeof (ctx));
     536            1 :   FAILIF (0 >=
     537              :           TALER_EXCHANGEDB_iterate_aml_history (pg,
     538              :                                                 &account.h_normalized,
     539              :                                                 0,
     540              :                                                 10,
     541              :                                                 &history_cb,
     542              :                                                 &ctx));
     543            1 :   FAILIF_C (1 != ctx.total,
     544              :             GNUNET_free (ctx.justification));
     545            1 :   FAILIF_C (0 != strcmp (ctx.justification,
     546              :                          "looks fine"),
     547              :             GNUNET_free (ctx.justification));
     548            1 :   FAILIF_C (! ctx.is_active,
     549              :             GNUNET_free (ctx.justification));
     550            1 :   FAILIF_C (! ctx.have_officer,
     551              :             GNUNET_free (ctx.justification));
     552            1 :   GNUNET_free (ctx.justification);
     553            1 :   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            1 : 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            1 :   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            1 :   FAILIF (GNUNET_TIME_timestamp_cmp (st.last_date,
     583              :                                      !=,
     584              :                                      ts (1600000000)));
     585            1 :   FAILIF (1 != TDB_count (pg,
     586              :                           "FROM aml_history"));
     587              : 
     588              :   /* a newer one is taken and deactivates the previous outcome */
     589            1 :   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            1 :   FAILIF (2 != TDB_count (pg,
     600              :                           "FROM aml_history"));
     601            1 :   FAILIF (2 != TDB_count (pg,
     602              :                           "FROM legitimization_outcomes"));
     603            1 :   FAILIF (1 != TDB_count (pg,
     604              :                           "FROM legitimization_outcomes"
     605              :                           " WHERE is_active"));
     606              : 
     607            1 :   memset (&ctx,
     608              :           0,
     609              :           sizeof (ctx));
     610            1 :   FAILIF (0 >=
     611              :           TALER_EXCHANGEDB_iterate_aml_history (pg,
     612              :                                                 &account.h_normalized,
     613              :                                                 0,
     614              :                                                 10,
     615              :                                                 &history_cb,
     616              :                                                 &ctx));
     617            1 :   FAILIF_C (2 != ctx.total,
     618              :             GNUNET_free (ctx.justification));
     619            1 :   GNUNET_free (ctx.justification);
     620            1 :   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            1 : 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            1 :   TDB_FILL (master_sig,
     639              :             30);
     640            1 :   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            1 :   memset (&ctx,
     651              :           0,
     652              :           sizeof (ctx));
     653            1 :   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            1 :   FAILIF_C (2 != ctx.total,
     663              :             GNUNET_free (ctx.justification));
     664            1 :   FAILIF_C (NULL == ctx.new_measures,
     665              :             GNUNET_free (ctx.justification));
     666            1 :   FAILIF_C (0 != strcmp (ctx.new_measures,
     667              :                          "+review-a review-b"),
     668              :             GNUNET_free (ctx.justification));
     669            1 :   FAILIF_C (NULL == ctx.officer_name,
     670              :             GNUNET_free (ctx.justification));
     671            1 :   FAILIF_C (0 != strcmp (ctx.officer_name,
     672              :                          "Alex Officer"),
     673              :             GNUNET_free (ctx.justification));
     674            1 :   FAILIF_C (! ctx.have_officer,
     675              :             GNUNET_free (ctx.justification));
     676            1 :   FAILIF_C (! ctx.have_attributes,
     677              :             GNUNET_free (ctx.justification));
     678            1 :   GNUNET_free (ctx.justification);
     679            1 :   GNUNET_free (ctx.new_measures);
     680            1 :   GNUNET_free (ctx.officer_name);
     681              : 
     682              :   /* only the newer decision asked for an investigation */
     683            1 :   memset (&ctx,
     684              :           0,
     685              :           sizeof (ctx));
     686            1 :   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            1 :   FAILIF_C (1 != ctx.total,
     696              :             GNUNET_free (ctx.justification));
     697            1 :   FAILIF_C (0 != strcmp (ctx.justification,
     698              :                          "investigate this"),
     699              :             GNUNET_free (ctx.justification));
     700            1 :   GNUNET_free (ctx.justification);
     701            1 :   GNUNET_free (ctx.new_measures);
     702            1 :   GNUNET_free (ctx.officer_name);
     703              : 
     704              :   /* and only one of them is the active outcome */
     705            1 :   memset (&ctx,
     706              :           0,
     707              :           sizeof (ctx));
     708            1 :   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            1 :   FAILIF_C (1 != ctx.total,
     718              :             GNUNET_free (ctx.justification));
     719            1 :   FAILIF_C (! ctx.is_active,
     720              :             GNUNET_free (ctx.justification));
     721            1 :   GNUNET_free (ctx.justification);
     722            1 :   GNUNET_free (ctx.new_measures);
     723            1 :   GNUNET_free (ctx.officer_name);
     724              : 
     725              :   /* an account nobody decided about has nothing */
     726              :   {
     727              :     struct TALER_NormalizedPaytoHashP other;
     728              : 
     729            1 :     TDB_FILL (other,
     730              :               98);
     731            1 :     memset (&ctx,
     732              :             0,
     733              :             sizeof (ctx));
     734            1 :     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            1 :     FAILIF (0 != ctx.total);
     744              :   }
     745            1 :   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            1 : check_serial (struct TALER_EXCHANGEDB_PostgresContext *pg)
     758              : {
     759              :   struct HistoryContext ctx;
     760              : 
     761            1 :   memset (&ctx,
     762              :           0,
     763              :           sizeof (ctx));
     764            1 :   FAILIF (0 >=
     765              :           TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
     766              :             pg,
     767              :             0,
     768              :             &serial_cb,
     769              :             &ctx));
     770            1 :   FAILIF_C (2 != ctx.total,
     771              :             GNUNET_free (ctx.justification));
     772            1 :   FAILIF_C (! ctx.have_officer,
     773              :             GNUNET_free (ctx.justification));
     774            1 :   GNUNET_free (ctx.justification);
     775              : 
     776            1 :   memset (&ctx,
     777              :           0,
     778              :           sizeof (ctx));
     779            1 :   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            1 :   FAILIF (0 != ctx.total);
     786              : 
     787            1 :   memset (&ctx,
     788              :           0,
     789              :           sizeof (ctx));
     790            1 :   ctx.stop_after = 1;
     791            1 :   FAILIF (0 >=
     792              :           TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
     793              :             pg,
     794              :             0,
     795              :             &serial_cb,
     796              :             &ctx));
     797            1 :   FAILIF_C (1 != ctx.total,
     798              :             GNUNET_free (ctx.justification));
     799            1 :   GNUNET_free (ctx.justification);
     800            1 :   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            1 : check_history_builder (struct TALER_EXCHANGEDB_PostgresContext *pg)
     812              : {
     813              :   struct TALER_AttributeEncryptionKeyP attribute_key;
     814            1 :   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            1 :   TDB_FILL (attribute_key,
     823              :             1);
     824            1 :   j = TALER_EXCHANGEDB_aml_history_builder (&hbc);
     825            1 :   FAILIF (NULL == j);
     826            1 :   FAILIF_C (! json_is_array (j),
     827              :             json_decref (j));
     828            1 :   FAILIF_C (2 != json_array_size (j),
     829              :             json_decref (j));
     830            1 :   json_decref (j);
     831            1 :   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            1 : check_automated_outcome (struct TALER_EXCHANGEDB_PostgresContext *pg)
     843              : {
     844              :   struct DecisionStatus st;
     845            1 :   struct HistoryContext ctx = { 0 };
     846              : 
     847            1 :   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            1 :   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            1 :   FAILIF_C (1 != ctx.total,
     867              :             GNUNET_free (ctx.justification));
     868            1 :   FAILIF_C (ctx.have_officer,
     869              :             GNUNET_free (ctx.justification));
     870            1 :   FAILIF_C (NULL != ctx.officer_name,
     871              :             GNUNET_free (ctx.justification));
     872            1 :   GNUNET_free (ctx.justification);
     873            1 :   GNUNET_free (ctx.new_measures);
     874            1 :   GNUNET_free (ctx.officer_name);
     875            1 :   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            1 : main (int argc,
     905              :       char *const *argv)
     906              : {
     907              :   int ret;
     908              : 
     909            1 :   ret = TDB_main (argc,
     910              :                   argv,
     911              :                   "test-aml-history",
     912              :                   "Tests for the exchangedb `aml_history' table",
     913              :                   tests);
     914            1 :   TDB_account_free (&account);
     915            1 :   return ret;
     916              : }
     917              : 
     918              : 
     919              : /* end of test_aml_history.c */
        

Generated by: LCOV version 2.0-1