exchange

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

test_legitimization_measures.c (27725B)


      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_legitimization_measures.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `legitimization_measures`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_do_trigger_kyc_rule_for_account(),
     23  * #TALER_EXCHANGEDB_insert_active_legitimization_measure(),
     24  * #TALER_EXCHANGEDB_insert_successor_measure(),
     25  * #TALER_EXCHANGEDB_get_pending_legitimization(),
     26  * #TALER_EXCHANGEDB_get_active_legitimization(),
     27  * #TALER_EXCHANGEDB_get_completed_legitimization(),
     28  * #TALER_EXCHANGEDB_get_kyc_status_by_token(),
     29  * #TALER_EXCHANGEDB_get_open_legitimization_measure(),
     30  * #TALER_EXCHANGEDB_get_legitimization_requirement_by_row() and
     31  * #TALER_EXCHANGEDB_iterate_aml_measures().
     32  *
     33  * A row of `legitimization_measures` is one set of KYC measures the
     34  * exchange put on an account.  It is keyed by the account's access token,
     35  * so `kyc_targets` has to exist first -- do_trigger_kyc_rule_for_account()
     36  * creates it when it has to.  Only one measure set per account is active
     37  * at a time: triggering a different set finishes the previous one.
     38  */
     39 #include "test_common.h"
     40 #include "exchange-database/do_trigger_kyc_rule_for_account.h"
     41 #include "exchange-database/get_active_legitimization.h"
     42 #include "exchange-database/get_completed_legitimization.h"
     43 #include "exchange-database/get_kyc_status_by_token.h"
     44 #include "exchange-database/get_legitimization_requirement_by_row.h"
     45 #include "exchange-database/get_open_legitimization_measure.h"
     46 #include "exchange-database/get_pending_legitimization.h"
     47 #include "exchange-database/insert_active_legitimization_measure.h"
     48 #include "exchange-database/insert_legitimization_process.h"
     49 #include "exchange-database/insert_successor_measure.h"
     50 #include "exchange-database/iterate_aml_measures.h"
     51 
     52 
     53 /**
     54  * Account the checks put measures on.
     55  */
     56 static struct TDB_Account account;
     57 
     58 
     59 /**
     60  * Row of the measure set that check_trigger() created.
     61  */
     62 static uint64_t measure_row;
     63 
     64 
     65 /**
     66  * Key the account may use to authorise KYC work on itself.
     67  */
     68 static union TALER_AccountPublicKeyP account_pub;
     69 
     70 
     71 /**
     72  * Build a timestamp from a number of seconds since the epoch.
     73  *
     74  * @param secs seconds since the epoch
     75  * @return the timestamp
     76  */
     77 static struct GNUNET_TIME_Timestamp
     78 ts (uint64_t secs)
     79 {
     80   struct GNUNET_TIME_Absolute abs = {
     81     .abs_value_us = secs * 1000LLU * 1000LLU
     82   };
     83 
     84   return GNUNET_TIME_absolute_to_timestamp (abs);
     85 }
     86 
     87 
     88 /**
     89  * Build a measure set naming @a check.
     90  *
     91  * @param check name of the check to demand
     92  * @return the measure set, to be freed with json_decref()
     93  */
     94 static json_t *
     95 make_measures (const char *check)
     96 {
     97   json_t *j;
     98 
     99   j = GNUNET_JSON_PACK (
    100     GNUNET_JSON_pack_array_steal (
    101       "measures",
    102       json_pack ("[{s:s,s:s,s:s}]",
    103                  "check_name",
    104                  check,
    105                  "prog_name",
    106                  "skip",
    107                  "context",
    108                  "none")),
    109     GNUNET_JSON_pack_bool ("is_and_combinator",
    110                            true));
    111   GNUNET_assert (NULL != j);
    112   return j;
    113 }
    114 
    115 
    116 /**
    117  * Read the access token the database generated for our account.
    118  *
    119  * @param pg the database context
    120  * @param[out] access_token set to the account's access token
    121  */
    122 static void
    123 get_access_token (struct TALER_EXCHANGEDB_PostgresContext *pg,
    124                   struct TALER_AccountAccessTokenP *access_token)
    125 {
    126   struct GNUNET_PQ_QueryParam params[] = {
    127     GNUNET_PQ_query_param_auto_from_type (&account.h_normalized),
    128     GNUNET_PQ_query_param_end
    129   };
    130   struct GNUNET_PQ_ResultSpec rs[] = {
    131     GNUNET_PQ_result_spec_auto_from_type ("access_token",
    132                                           access_token),
    133     GNUNET_PQ_result_spec_end
    134   };
    135 
    136   GNUNET_assert (GNUNET_OK ==
    137                  GNUNET_PQ_prepare_anon (pg->conn,
    138                                          "SELECT access_token"
    139                                          " FROM kyc_targets"
    140                                          " WHERE h_normalized_payto=$1;"));
    141   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    142                  GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
    143                                                            "",
    144                                                            params,
    145                                                            rs));
    146 }
    147 
    148 
    149 /**
    150  * Closure for #measure_cb().
    151  */
    152 struct MeasureContext
    153 {
    154   /**
    155    * How many measures did the callback see?
    156    */
    157   unsigned int total;
    158 
    159   /**
    160    * Row we are looking for, 0 to match nothing.
    161    */
    162   uint64_t measure_row;
    163 
    164   /**
    165    * How many times did we see it?
    166    */
    167   unsigned int matched;
    168 
    169   /**
    170    * Was it reported as finished?
    171    */
    172   bool is_finished;
    173 
    174   /**
    175    * Account it was reported for.
    176    */
    177   struct TALER_NormalizedPaytoHashP h_payto;
    178 };
    179 
    180 
    181 /**
    182  * Callback for #TALER_EXCHANGEDB_iterate_aml_measures().
    183  *
    184  * @param cls a `struct MeasureContext *`
    185  * @param h_payto account the measure applies to
    186  * @param start_time when the measure was imposed
    187  * @param jmeasures the measures
    188  * @param is_finished whether the measure was completed
    189  * @param measure_serial_id row of the measure
    190  */
    191 static void
    192 measure_cb (void *cls,
    193             struct TALER_NormalizedPaytoHashP *h_payto,
    194             struct GNUNET_TIME_Absolute start_time,
    195             const json_t *jmeasures,
    196             bool is_finished,
    197             uint64_t measure_serial_id)
    198 {
    199   struct MeasureContext *ctx = cls;
    200 
    201   (void) start_time;
    202   (void) jmeasures;
    203   ctx->total++;
    204   if ( (0 != ctx->measure_row) &&
    205        (measure_serial_id == ctx->measure_row) )
    206   {
    207     ctx->matched++;
    208     ctx->is_finished = is_finished;
    209     ctx->h_payto = *h_payto;
    210   }
    211 }
    212 
    213 
    214 /**
    215  * Nothing is known while the table is empty.
    216  *
    217  * @param pg the database context
    218  * @return 0 on success
    219  */
    220 static int
    221 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    222 {
    223   struct TALER_NormalizedPaytoHashP h_payto;
    224   struct TALER_AccountAccessTokenP access_token;
    225   struct GNUNET_TIME_Absolute start_time;
    226   struct MeasureContext ctx = { 0 };
    227   json_t *jmeasures = NULL;
    228   uint64_t row;
    229   bool is_finished;
    230   bool is_wallet;
    231 
    232   TDB_FILL (h_payto,
    233             1);
    234   TDB_FILL (access_token,
    235             1);
    236   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    237           TALER_EXCHANGEDB_get_pending_legitimization (pg,
    238                                                        1,
    239                                                        &access_token,
    240                                                        &h_payto,
    241                                                        &jmeasures,
    242                                                        &is_finished,
    243                                                        &is_wallet));
    244   FAILIF (NULL != jmeasures);
    245   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    246           TALER_EXCHANGEDB_get_kyc_status_by_token (pg,
    247                                                     &access_token,
    248                                                     &row,
    249                                                     &jmeasures));
    250   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    251           TALER_EXCHANGEDB_get_open_legitimization_measure (pg,
    252                                                             &h_payto,
    253                                                             &row,
    254                                                             &start_time));
    255   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    256           TALER_EXCHANGEDB_iterate_aml_measures (pg,
    257                                                  NULL,
    258                                                  TALER_EXCHANGE_YNA_ALL,
    259                                                  0,
    260                                                  10,
    261                                                  &measure_cb,
    262                                                  &ctx));
    263   FAILIF (0 != ctx.total);
    264   return 0;
    265 }
    266 
    267 
    268 /**
    269  * Triggering a rule creates the account and the measure set.
    270  *
    271  * @param pg the database context
    272  * @return 0 on success
    273  */
    274 static int
    275 check_trigger (struct TALER_EXCHANGEDB_PostgresContext *pg)
    276 {
    277   struct TALER_NormalizedPayto npayto;
    278   struct TALER_FullPayto payto;
    279   struct TALER_NormalizedPaytoHashP h_payto;
    280   struct TALER_AccountAccessTokenP access_token;
    281   struct TALER_AccountAccessTokenP got_token;
    282   struct TALER_NormalizedPaytoHashP got_payto;
    283   json_t *jmeasures = make_measures ("verify-id");
    284   json_t *got = NULL;
    285   uint64_t row = 0;
    286   bool bad_kyc_auth = false;
    287   bool is_finished = true;
    288   bool is_wallet = true;
    289 
    290   payto.full_payto = (char *)
    291                      "payto://x-taler-bank/localhost/lm?receiver-name=LM";
    292   TALER_full_payto_hash (payto,
    293                          &account.h_full);
    294   npayto = TALER_payto_normalize (payto);
    295   GNUNET_assert (NULL != npayto.normalized_payto);
    296   TALER_normalized_payto_hash (npayto,
    297                                &h_payto);
    298   GNUNET_free (npayto.normalized_payto);
    299   account.payto.full_payto = GNUNET_strdup (payto.full_payto);
    300   account.h_normalized = h_payto;
    301 
    302   TDB_FILL (account_pub,
    303             42);
    304   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    305             TALER_EXCHANGEDB_do_trigger_kyc_rule_for_account (
    306               pg,
    307               payto,
    308               &h_payto,
    309               &account_pub,
    310               NULL,
    311               jmeasures,
    312               1,
    313               &row,
    314               &bad_kyc_auth),
    315             json_decref (jmeasures));
    316   FAILIF_C (0 == row,
    317             json_decref (jmeasures));
    318   /* The account was not known, so there was no key to match against; the
    319      function reports that even though it just set one. */
    320   FAILIF_C (! bad_kyc_auth,
    321             json_decref (jmeasures));
    322   measure_row = row;
    323   FAILIF_C (1 != TDB_count (pg,
    324                             "FROM legitimization_measures"),
    325             json_decref (jmeasures));
    326   /* the account was created along the way */
    327   FAILIF_C (1 != TDB_count (pg,
    328                             "FROM kyc_targets"),
    329             json_decref (jmeasures));
    330   FAILIF_C (1 != TDB_count (pg,
    331                             "FROM wire_targets"),
    332             json_decref (jmeasures));
    333 
    334   /* the measure set can be read back by its row... */
    335   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    336             TALER_EXCHANGEDB_get_pending_legitimization (pg,
    337                                                          row,
    338                                                          &got_token,
    339                                                          &got_payto,
    340                                                          &got,
    341                                                          &is_finished,
    342                                                          &is_wallet),
    343             json_decref (jmeasures));
    344   FAILIF_C (0 != GNUNET_memcmp (&got_payto,
    345                                 &h_payto),
    346             json_decref (got); json_decref (jmeasures));
    347   FAILIF_C (is_finished,
    348             json_decref (got); json_decref (jmeasures));
    349   FAILIF_C (is_wallet,
    350             json_decref (got); json_decref (jmeasures));
    351   FAILIF_C (1 != json_equal (got,
    352                              jmeasures),
    353             json_decref (got); json_decref (jmeasures));
    354   json_decref (got);
    355   got = NULL;
    356 
    357   /* ...and by the account's access token */
    358   get_access_token (pg,
    359                     &access_token);
    360   FAILIF_C (0 != GNUNET_memcmp (&got_token,
    361                                 &access_token),
    362             json_decref (jmeasures));
    363   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    364             TALER_EXCHANGEDB_get_kyc_status_by_token (pg,
    365                                                       &access_token,
    366                                                       &row,
    367                                                       &got),
    368             json_decref (jmeasures));
    369   FAILIF_C (row != measure_row,
    370             json_decref (got); json_decref (jmeasures));
    371   json_decref (got);
    372 
    373   /* triggering the very same measure set again keeps the one row */
    374   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    375             TALER_EXCHANGEDB_do_trigger_kyc_rule_for_account (
    376               pg,
    377               payto,
    378               &h_payto,
    379               NULL,
    380               NULL,
    381               jmeasures,
    382               1,
    383               &row,
    384               &bad_kyc_auth),
    385             json_decref (jmeasures));
    386   FAILIF_C (row != measure_row,
    387             json_decref (jmeasures));
    388   FAILIF_C (1 != TDB_count (pg,
    389                             "FROM legitimization_measures"),
    390             json_decref (jmeasures));
    391   json_decref (jmeasures);
    392   return 0;
    393 }
    394 
    395 
    396 /**
    397  * A different measure set supersedes the previous one, which is marked
    398  * finished.
    399  *
    400  * @param pg the database context
    401  * @return 0 on success
    402  */
    403 static int
    404 check_supersede (struct TALER_EXCHANGEDB_PostgresContext *pg)
    405 {
    406   json_t *jmeasures = make_measures ("verify-address");
    407   uint64_t row = 0;
    408   bool bad_kyc_auth = false;
    409 
    410   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    411             TALER_EXCHANGEDB_do_trigger_kyc_rule_for_account (
    412               pg,
    413               account.payto,
    414               &account.h_normalized,
    415               NULL,
    416               NULL,
    417               jmeasures,
    418               2,
    419               &row,
    420               &bad_kyc_auth),
    421             json_decref (jmeasures));
    422   json_decref (jmeasures);
    423   FAILIF (row == measure_row);
    424   FAILIF (2 != TDB_count (pg,
    425                           "FROM legitimization_measures"));
    426   FAILIF (1 != TDB_count (pg,
    427                           "FROM legitimization_measures"
    428                           " WHERE is_finished"));
    429   measure_row = row;
    430   return 0;
    431 }
    432 
    433 
    434 /**
    435  * The account's rule requirement and its open measure are reported.
    436  *
    437  * @param pg the database context
    438  * @return 0 on success
    439  */
    440 static int
    441 check_requirement (struct TALER_EXCHANGEDB_PostgresContext *pg)
    442 {
    443   struct TALER_AccountAccessTokenP access_token;
    444   struct TALER_AccountAccessTokenP got_token;
    445   struct GNUNET_TIME_Absolute start_time;
    446   enum GNUNET_GenericReturnValue is_wallet;
    447   json_t *jrules = NULL;
    448   uint64_t rule_gen = 42;
    449   uint64_t row = 0;
    450   bool aml_review = true;
    451   bool kyc_required = false;
    452 
    453   get_access_token (pg,
    454                     &access_token);
    455   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    456           TALER_EXCHANGEDB_get_legitimization_requirement_by_row (
    457             pg,
    458             &account.h_normalized,
    459             &account_pub,
    460             &is_wallet,
    461             &got_token,
    462             &rule_gen,
    463             &jrules,
    464             &aml_review,
    465             &kyc_required));
    466   FAILIF_C (0 != GNUNET_memcmp (&got_token,
    467                                 &access_token),
    468             json_decref (jrules));
    469   FAILIF_C (GNUNET_NO != is_wallet,
    470             json_decref (jrules));
    471   /* no AML decision has been taken for the account */
    472   FAILIF_C (aml_review,
    473             json_decref (jrules));
    474   FAILIF_C (0 != rule_gen,
    475             json_decref (jrules));
    476   /* ...but a measure is pending, so KYC is required */
    477   FAILIF_C (! kyc_required,
    478             json_decref (jrules));
    479   json_decref (jrules);
    480 
    481   /* the exchange's own account of why it is holding back */
    482   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    483           TALER_EXCHANGEDB_get_open_legitimization_measure (
    484             pg,
    485             &account.h_normalized,
    486             &row,
    487             &start_time));
    488   /* the oldest undecided measure is the one from check_trigger() */
    489   FAILIF (0 == row);
    490   FAILIF (GNUNET_TIME_absolute_is_zero (start_time));
    491 
    492   /* an account nobody put a measure on has none */
    493   {
    494     struct TALER_NormalizedPaytoHashP other;
    495 
    496     TDB_FILL (other,
    497               98);
    498     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    499             TALER_EXCHANGEDB_get_open_legitimization_measure (pg,
    500                                                               &other,
    501                                                               &row,
    502                                                               &start_time));
    503   }
    504   return 0;
    505 }
    506 
    507 
    508 /**
    509  * A legitimization process ties a measure set to the check it is
    510  * satisfying.
    511  *
    512  * @param pg the database context
    513  * @return 0 on success
    514  */
    515 static int
    516 check_process (struct TALER_EXCHANGEDB_PostgresContext *pg)
    517 {
    518   struct TALER_AccountAccessTokenP access_token;
    519   struct TALER_NormalizedPaytoHashP h_payto;
    520   json_t *jmeasures = NULL;
    521   size_t attributes_len = 42;
    522   void *attributes = NULL;
    523   uint64_t process_row = 0;
    524   uint32_t measure_index = 99;
    525   bool is_finished = true;
    526   bool is_wallet = true;
    527 
    528   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    529           TALER_EXCHANGEDB_insert_legitimization_process (
    530             pg,
    531             &account.h_normalized,
    532             0,
    533             measure_row,
    534             "test-provider",
    535             "provider-account-1",
    536             "provider-legi-1",
    537             &process_row));
    538   FAILIF (0 == process_row);
    539 
    540   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    541           TALER_EXCHANGEDB_get_active_legitimization (pg,
    542                                                       process_row,
    543                                                       &measure_index,
    544                                                       &jmeasures));
    545   FAILIF_C (0 != measure_index,
    546             json_decref (jmeasures));
    547   FAILIF_C (NULL == jmeasures,
    548             json_decref (jmeasures));
    549   json_decref (jmeasures);
    550   jmeasures = NULL;
    551 
    552   /* a process that does not exist has no active measure */
    553   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    554           TALER_EXCHANGEDB_get_active_legitimization (pg,
    555                                                       process_row + 1000,
    556                                                       &measure_index,
    557                                                       &jmeasures));
    558 
    559   /* the completed view reaches the same measure through the process */
    560   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    561           TALER_EXCHANGEDB_get_completed_legitimization (pg,
    562                                                          measure_row,
    563                                                          0,
    564                                                          &access_token,
    565                                                          &h_payto,
    566                                                          &is_wallet,
    567                                                          &jmeasures,
    568                                                          &is_finished,
    569                                                          &attributes_len,
    570                                                          &attributes));
    571   FAILIF_C (0 != GNUNET_memcmp (&h_payto,
    572                                 &account.h_normalized),
    573             json_decref (jmeasures); GNUNET_free (attributes));
    574   FAILIF_C (is_finished,
    575             json_decref (jmeasures); GNUNET_free (attributes));
    576   /* no attributes have been collected yet */
    577   FAILIF_C (NULL != attributes,
    578             json_decref (jmeasures); GNUNET_free (attributes));
    579   json_decref (jmeasures);
    580   jmeasures = NULL;
    581 
    582   /* a measure index no process is working on is not found */
    583   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    584           TALER_EXCHANGEDB_get_completed_legitimization (pg,
    585                                                          measure_row,
    586                                                          7,
    587                                                          &access_token,
    588                                                          &h_payto,
    589                                                          &is_wallet,
    590                                                          &jmeasures,
    591                                                          &is_finished,
    592                                                          &attributes_len,
    593                                                          &attributes));
    594   return 0;
    595 }
    596 
    597 
    598 /**
    599  * A measure can also be created directly against an access token.
    600  *
    601  * @param pg the database context
    602  * @return 0 on success
    603  */
    604 static int
    605 check_insert_active (struct TALER_EXCHANGEDB_PostgresContext *pg)
    606 {
    607   struct TALER_AccountAccessTokenP access_token;
    608   json_t *jmeasures = make_measures ("verify-phone");
    609   uint64_t row = 0;
    610 
    611   get_access_token (pg,
    612                     &access_token);
    613   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    614             TALER_EXCHANGEDB_insert_active_legitimization_measure (
    615               pg,
    616               &access_token,
    617               jmeasures,
    618               &row),
    619             json_decref (jmeasures));
    620   json_decref (jmeasures);
    621   FAILIF (0 == row);
    622   FAILIF (3 != TDB_count (pg,
    623                           "FROM legitimization_measures"));
    624   /* it becomes the one active measure for the account */
    625   FAILIF (1 != TDB_count (pg,
    626                           "FROM legitimization_measures"
    627                           " WHERE NOT is_finished"));
    628   measure_row = row;
    629 
    630   return 0;
    631 }
    632 
    633 
    634 /**
    635  * A successor measure replaces what is active for the account.
    636  *
    637  * @param pg the database context
    638  * @return 0 on success
    639  */
    640 static int
    641 check_successor (struct TALER_EXCHANGEDB_PostgresContext *pg)
    642 {
    643   struct GNUNET_TIME_Timestamp last_date;
    644   json_t *jmeasures = make_measures ("verify-income");
    645   bool unknown_account = true;
    646 
    647   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    648             TALER_EXCHANGEDB_insert_successor_measure (
    649               pg,
    650               &account.h_normalized,
    651               ts (1600000000),
    652               "verify-income",
    653               jmeasures,
    654               &unknown_account,
    655               &last_date),
    656             json_decref (jmeasures));
    657   FAILIF_C (unknown_account,
    658             json_decref (jmeasures));
    659   FAILIF_C (4 != TDB_count (pg,
    660                             "FROM legitimization_measures"),
    661             json_decref (jmeasures));
    662 
    663   /* an older decision is ignored */
    664   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    665             TALER_EXCHANGEDB_insert_successor_measure (
    666               pg,
    667               &account.h_normalized,
    668               ts (1500000000),
    669               "verify-income",
    670               jmeasures,
    671               &unknown_account,
    672               &last_date),
    673             json_decref (jmeasures));
    674   FAILIF_C (GNUNET_TIME_timestamp_cmp (last_date,
    675                                        !=,
    676                                        ts (1600000000)),
    677             json_decref (jmeasures));
    678   FAILIF_C (4 != TDB_count (pg,
    679                             "FROM legitimization_measures"),
    680             json_decref (jmeasures));
    681 
    682   /* an account the exchange never saw is reported as unknown */
    683   {
    684     struct TALER_NormalizedPaytoHashP other;
    685 
    686     TDB_FILL (other,
    687               95);
    688     unknown_account = false;
    689     FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    690               TALER_EXCHANGEDB_insert_successor_measure (
    691                 pg,
    692                 &other,
    693                 ts (1600000000),
    694                 "verify-income",
    695                 jmeasures,
    696                 &unknown_account,
    697                 &last_date),
    698               json_decref (jmeasures));
    699     FAILIF_C (! unknown_account,
    700               json_decref (jmeasures));
    701   }
    702   json_decref (jmeasures);
    703   return 0;
    704 }
    705 
    706 
    707 /**
    708  * The measure listing reports the account's measures, filtered.
    709  *
    710  * @param pg the database context
    711  * @return 0 on success
    712  */
    713 static int
    714 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
    715 {
    716   struct MeasureContext ctx;
    717   unsigned int all;
    718 
    719   memset (&ctx,
    720           0,
    721           sizeof (ctx));
    722   FAILIF (0 >=
    723           TALER_EXCHANGEDB_iterate_aml_measures (pg,
    724                                                  &account.h_normalized,
    725                                                  TALER_EXCHANGE_YNA_ALL,
    726                                                  0,
    727                                                  100,
    728                                                  &measure_cb,
    729                                                  &ctx));
    730   all = ctx.total;
    731   FAILIF (0 == all);
    732 
    733   /* the account filter matters */
    734   {
    735     struct TALER_NormalizedPaytoHashP other;
    736 
    737     TDB_FILL (other,
    738               94);
    739     memset (&ctx,
    740             0,
    741             sizeof (ctx));
    742     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    743             TALER_EXCHANGEDB_iterate_aml_measures (pg,
    744                                                    &other,
    745                                                    TALER_EXCHANGE_YNA_ALL,
    746                                                    0,
    747                                                    100,
    748                                                    &measure_cb,
    749                                                    &ctx));
    750     FAILIF (0 != ctx.total);
    751   }
    752 
    753   /* only one measure is still open */
    754   memset (&ctx,
    755           0,
    756           sizeof (ctx));
    757   FAILIF (0 >=
    758           TALER_EXCHANGEDB_iterate_aml_measures (pg,
    759                                                  &account.h_normalized,
    760                                                  TALER_EXCHANGE_YNA_YES,
    761                                                  0,
    762                                                  100,
    763                                                  &measure_cb,
    764                                                  &ctx));
    765   FAILIF (1 != ctx.total);
    766 
    767   /* the finished ones make up the rest */
    768   memset (&ctx,
    769           0,
    770           sizeof (ctx));
    771   FAILIF (0 >=
    772           TALER_EXCHANGEDB_iterate_aml_measures (pg,
    773                                                  &account.h_normalized,
    774                                                  TALER_EXCHANGE_YNA_NO,
    775                                                  0,
    776                                                  100,
    777                                                  &measure_cb,
    778                                                  &ctx));
    779   FAILIF (all - 1 != ctx.total);
    780 
    781   /* the limit caps the result set */
    782   memset (&ctx,
    783           0,
    784           sizeof (ctx));
    785   FAILIF (1 !=
    786           TALER_EXCHANGEDB_iterate_aml_measures (pg,
    787                                                  NULL,
    788                                                  TALER_EXCHANGE_YNA_ALL,
    789                                                  0,
    790                                                  1,
    791                                                  &measure_cb,
    792                                                  &ctx));
    793   FAILIF (1 != ctx.total);
    794   return 0;
    795 }
    796 
    797 
    798 /**
    799  * The checks to run, in order.
    800  */
    801 static const struct TDB_Test tests[] = {
    802   { "legitimization-measures-empty",
    803     &check_empty },
    804   { "legitimization-measures-trigger",
    805     &check_trigger },
    806   { "legitimization-measures-supersede",
    807     &check_supersede },
    808   { "legitimization-measures-requirement",
    809     &check_requirement },
    810   { "legitimization-measures-process",
    811     &check_process },
    812   { "legitimization-measures-insert-active",
    813     &check_insert_active },
    814   { "legitimization-measures-successor",
    815     &check_successor },
    816   { "legitimization-measures-iterate",
    817     &check_iterate },
    818   { NULL, NULL }
    819 };
    820 
    821 
    822 int
    823 main (int argc,
    824       char *const *argv)
    825 {
    826   int ret;
    827 
    828   ret = TDB_main (argc,
    829                   argv,
    830                   "test-legitimization-measures",
    831                   "Tests for the exchangedb `legitimization_measures' table",
    832                   tests);
    833   TDB_account_free (&account);
    834   return ret;
    835 }
    836 
    837 
    838 /* end of test_legitimization_measures.c */