exchange

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

test_kyc_attributes.c (30267B)


      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_kyc_attributes.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `kyc_attributes`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_do_insert_kyc_attributes(),
     23  * #TALER_EXCHANGEDB_iterate_kyc_attributes(),
     24  * #TALER_EXCHANGEDB_iterate_unscreened_kyc_attributes(),
     25  * #TALER_EXCHANGEDB_update_to_kyc_attributes_screened(),
     26  * #TALER_EXCHANGEDB_update_to_kyc_attributes_unscreened(),
     27  * #TALER_EXCHANGEDB_iterate_aml_attributes(),
     28  * #TALER_EXCHANGEDB_iterate_kyc_history(),
     29  * #TALER_EXCHANGEDB_kyc_history_builder() and
     30  * #TALER_EXCHANGEDB_current_attributes_builder().
     31  *
     32  * A `kyc_attributes` row is the (encrypted) personal data one KYC process
     33  * collected, so every row needs a `legitimization_processes` row to hang
     34  * off; those in turn need a legitimization measure on a known account.
     35  * That is what setup_account() builds before the first insert.  The AML
     36  * officer path is exercised too, because it is the only way to get a row
     37  * with `by_aml_officer` set, which is what the AML attribute iterator
     38  * reports on.
     39  */
     40 #include "test_common.h"
     41 #include "exchange-database/account_history.h"
     42 #include "exchange-database/do_insert_kyc_attributes.h"
     43 #include "exchange-database/do_trigger_kyc_rule_for_account.h"
     44 #include "exchange-database/insert_aml_decision.h"
     45 #include "exchange-database/insert_aml_officer.h"
     46 #include "exchange-database/insert_legitimization_process.h"
     47 #include "exchange-database/iterate_unscreened_kyc_attributes.h"
     48 #include "exchange-database/iterate_aml_attributes.h"
     49 #include "exchange-database/iterate_kyc_attributes.h"
     50 #include "exchange-database/iterate_kyc_history.h"
     51 #include "exchange-database/update_to_kyc_attributes_screened.h"
     52 #include "exchange-database/update_to_kyc_attributes_unscreened.h"
     53 
     54 
     55 /**
     56  * Account the attributes are about.
     57  */
     58 static struct TDB_Account account;
     59 
     60 
     61 /**
     62  * Row of the legitimization measures on @e account.
     63  */
     64 static uint64_t measure_row;
     65 
     66 
     67 /**
     68  * Rows of the two KYC processes we run on @e account.
     69  */
     70 static uint64_t process_row[2];
     71 
     72 
     73 /**
     74  * Key the attributes are encrypted with.
     75  */
     76 static struct TALER_AttributeEncryptionKeyP attribute_key;
     77 
     78 
     79 /**
     80  * Name the AML officer is on file under.
     81  */
     82 #define OFFICER_NAME "Alex Officer"
     83 
     84 
     85 /**
     86  * Build the attribute set of @a name for storage.
     87  *
     88  * @param name value of the NAME attribute
     89  * @param[out] enc set to the encrypted attributes, to be freed by the
     90  *        caller
     91  * @param[out] enc_size set to the number of bytes in @a enc
     92  */
     93 static void
     94 encrypt_attributes (const char *name,
     95                     void **enc,
     96                     size_t *enc_size)
     97 {
     98   json_t *attrs;
     99 
    100   attrs = GNUNET_JSON_PACK (
    101     GNUNET_JSON_pack_string ("FORM_ID",
    102                              "test-form"),
    103     GNUNET_JSON_pack_string ("NAME",
    104                              name));
    105   GNUNET_assert (NULL != attrs);
    106   TALER_CRYPTO_kyc_attributes_encrypt (&attribute_key,
    107                                        attrs,
    108                                        enc,
    109                                        enc_size);
    110   json_decref (attrs);
    111 }
    112 
    113 
    114 /**
    115  * Closure for the various attribute callbacks.
    116  */
    117 struct AttributeContext
    118 {
    119   /**
    120    * How many rows did the callback see?
    121    */
    122   unsigned int total;
    123 
    124   /**
    125    * Stop after this many rows; 0 for no limit.
    126    */
    127   unsigned int stop_after;
    128 
    129   /**
    130    * Value of the NAME attribute of the last row seen, owned by this
    131    * struct.
    132    */
    133   char *name;
    134 
    135   /**
    136    * Provider of the last row seen, owned by this struct.
    137    */
    138   char *provider;
    139 
    140   /**
    141    * Officer of the last row seen, owned by this struct.
    142    */
    143   char *officer;
    144 
    145   /**
    146    * Row ID of the last row seen.
    147    */
    148   uint64_t row_id;
    149 
    150   /**
    151    * Was the last row filed by an AML officer?
    152    */
    153   bool by_aml_officer;
    154 
    155   /**
    156    * Was the KYC process of the last row finished?
    157    */
    158   bool finished;
    159 };
    160 
    161 
    162 /**
    163  * Release what a `struct AttributeContext` owns.
    164  *
    165  * @param[in,out] ctx context to clean up
    166  */
    167 static void
    168 ac_free (struct AttributeContext *ctx)
    169 {
    170   GNUNET_free (ctx->name);
    171   GNUNET_free (ctx->provider);
    172   GNUNET_free (ctx->officer);
    173 }
    174 
    175 
    176 /**
    177  * Decrypt @a enc and remember its NAME attribute in @a ctx.
    178  *
    179  * @param[in,out] ctx context to update
    180  * @param enc_size number of bytes in @a enc
    181  * @param enc encrypted attributes
    182  */
    183 static void
    184 remember_name (struct AttributeContext *ctx,
    185                size_t enc_size,
    186                const void *enc)
    187 {
    188   json_t *attrs;
    189   const char *name;
    190 
    191   GNUNET_free (ctx->name);
    192   ctx->name = NULL;
    193   attrs = TALER_CRYPTO_kyc_attributes_decrypt (&attribute_key,
    194                                                enc,
    195                                                enc_size);
    196   if (NULL == attrs)
    197     return;
    198   name = json_string_value (json_object_get (attrs,
    199                                              "NAME"));
    200   if (NULL != name)
    201     ctx->name = GNUNET_strdup (name);
    202   json_decref (attrs);
    203 }
    204 
    205 
    206 /**
    207  * Callback for #TALER_EXCHANGEDB_iterate_kyc_attributes().
    208  *
    209  * @param cls a `struct AttributeContext *`
    210  * @param h_payto account the attributes are about
    211  * @param provider_name provider that collected them
    212  * @param collection_time when they were collected
    213  * @param expiration_time when they expire
    214  * @param enc_attributes_size number of bytes in @a enc_attributes
    215  * @param enc_attributes the encrypted attributes
    216  */
    217 static void
    218 attribute_cb (void *cls,
    219               const struct TALER_NormalizedPaytoHashP *h_payto,
    220               const char *provider_name,
    221               struct GNUNET_TIME_Timestamp collection_time,
    222               struct GNUNET_TIME_Timestamp expiration_time,
    223               size_t enc_attributes_size,
    224               const void *enc_attributes)
    225 {
    226   struct AttributeContext *ctx = cls;
    227 
    228   (void) h_payto;
    229   (void) collection_time;
    230   (void) expiration_time;
    231   ctx->total++;
    232   GNUNET_free (ctx->provider);
    233   ctx->provider = (NULL == provider_name)
    234     ? NULL
    235     : GNUNET_strdup (provider_name);
    236   remember_name (ctx,
    237                  enc_attributes_size,
    238                  enc_attributes);
    239 }
    240 
    241 
    242 /**
    243  * Callback for #TALER_EXCHANGEDB_iterate_unscreened_kyc_attributes().
    244  *
    245  * @param cls a `struct AttributeContext *`
    246  * @param row_id row of the attributes
    247  * @param h_payto account the attributes are about
    248  * @param provider_name provider that collected them
    249  * @param collection_time when they were collected
    250  * @param expiration_time when they expire
    251  * @param properties properties set for the account
    252  * @param enc_attributes_size number of bytes in @a enc_attributes
    253  * @param enc_attributes the encrypted attributes
    254  * @return true to continue iterating
    255  */
    256 static bool
    257 all_attributes_cb (void *cls,
    258                    uint64_t row_id,
    259                    const struct TALER_NormalizedPaytoHashP *h_payto,
    260                    const char *provider_name,
    261                    struct GNUNET_TIME_Timestamp collection_time,
    262                    struct GNUNET_TIME_Timestamp expiration_time,
    263                    const json_t *properties,
    264                    size_t enc_attributes_size,
    265                    const void *enc_attributes)
    266 {
    267   struct AttributeContext *ctx = cls;
    268 
    269   (void) h_payto;
    270   (void) collection_time;
    271   (void) expiration_time;
    272   (void) properties;
    273   ctx->total++;
    274   ctx->row_id = row_id;
    275   GNUNET_free (ctx->provider);
    276   ctx->provider = (NULL == provider_name)
    277     ? NULL
    278     : GNUNET_strdup (provider_name);
    279   remember_name (ctx,
    280                  enc_attributes_size,
    281                  enc_attributes);
    282   return ! ( (0 != ctx->stop_after) &&
    283              (ctx->total >= ctx->stop_after) );
    284 }
    285 
    286 
    287 /**
    288  * Callback for #TALER_EXCHANGEDB_iterate_aml_attributes().
    289  *
    290  * @param cls a `struct AttributeContext *`
    291  * @param row_id row of the attributes
    292  * @param collection_time when they were collected
    293  * @param by_aml_officer were they filed by an AML officer
    294  * @param officer_name name of that officer, NULL if none
    295  * @param enc_attributes_size number of bytes in @a enc_attributes
    296  * @param enc_attributes the encrypted attributes
    297  */
    298 static void
    299 aml_attribute_cb (void *cls,
    300                   uint64_t row_id,
    301                   struct GNUNET_TIME_Timestamp collection_time,
    302                   bool by_aml_officer,
    303                   const char *officer_name,
    304                   size_t enc_attributes_size,
    305                   const void *enc_attributes)
    306 {
    307   struct AttributeContext *ctx = cls;
    308 
    309   (void) collection_time;
    310   ctx->total++;
    311   ctx->row_id = row_id;
    312   ctx->by_aml_officer = by_aml_officer;
    313   GNUNET_free (ctx->officer);
    314   ctx->officer = (NULL == officer_name)
    315     ? NULL
    316     : GNUNET_strdup (officer_name);
    317   remember_name (ctx,
    318                  enc_attributes_size,
    319                  enc_attributes);
    320 }
    321 
    322 
    323 /**
    324  * Callback for #TALER_EXCHANGEDB_iterate_kyc_history().
    325  *
    326  * @param cls a `struct AttributeContext *`
    327  * @param provider_name provider that ran the process
    328  * @param finished did the process finish
    329  * @param error_code error the process ran into
    330  * @param error_message message for @a error_code
    331  * @param provider_user_id account ID at the provider
    332  * @param provider_legitimization_id process ID at the provider
    333  * @param collection_time when the data was collected
    334  * @param expiration_time when the data expires
    335  * @param encrypted_attributes_len number of bytes in @a encrypted_attributes
    336  * @param encrypted_attributes the encrypted attributes
    337  */
    338 static void
    339 kyc_history_cb (void *cls,
    340                 const char *provider_name,
    341                 bool finished,
    342                 enum TALER_ErrorCode error_code,
    343                 const char *error_message,
    344                 const char *provider_user_id,
    345                 const char *provider_legitimization_id,
    346                 struct GNUNET_TIME_Timestamp collection_time,
    347                 struct GNUNET_TIME_Absolute expiration_time,
    348                 size_t encrypted_attributes_len,
    349                 const void *encrypted_attributes)
    350 {
    351   struct AttributeContext *ctx = cls;
    352 
    353   (void) error_code;
    354   (void) error_message;
    355   (void) provider_user_id;
    356   (void) provider_legitimization_id;
    357   (void) collection_time;
    358   (void) expiration_time;
    359   ctx->total++;
    360   ctx->finished = finished;
    361   GNUNET_free (ctx->provider);
    362   ctx->provider = (NULL == provider_name)
    363     ? NULL
    364     : GNUNET_strdup (provider_name);
    365   remember_name (ctx,
    366                  encrypted_attributes_len,
    367                  encrypted_attributes);
    368 }
    369 
    370 
    371 /**
    372  * Create the account, put a legitimization measure on it and start the
    373  * two KYC processes the attributes will belong to.
    374  *
    375  * @param pg the database context
    376  */
    377 static void
    378 setup_account (struct TALER_EXCHANGEDB_PostgresContext *pg)
    379 {
    380   json_t *jmeasures;
    381   bool bad_kyc_auth;
    382 
    383   if (0 != measure_row)
    384     return;
    385   TDB_FILL (attribute_key,
    386             42);
    387   TDB_account (pg,
    388                10,
    389                &account);
    390   jmeasures = GNUNET_JSON_PACK (
    391     GNUNET_JSON_pack_array_steal (
    392       "measures",
    393       json_pack ("[{s:s,s:s,s:s}]",
    394                  "check_name",
    395                  "verify-id",
    396                  "prog_name",
    397                  "skip",
    398                  "context",
    399                  "none")),
    400     GNUNET_JSON_pack_bool ("is_and_combinator",
    401                            true));
    402   GNUNET_assert (NULL != jmeasures);
    403   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    404                  TALER_EXCHANGEDB_do_trigger_kyc_rule_for_account (
    405                    pg,
    406                    account.payto,
    407                    &account.h_normalized,
    408                    NULL,
    409                    NULL,
    410                    jmeasures,
    411                    1,
    412                    &measure_row,
    413                    &bad_kyc_auth));
    414   json_decref (jmeasures);
    415   GNUNET_assert (0 != measure_row);
    416   for (unsigned int i = 0; i < 2; i++)
    417   {
    418     char *legi_id;
    419 
    420     GNUNET_asprintf (&legi_id,
    421                      "legi-%u",
    422                      i);
    423     GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    424                    TALER_EXCHANGEDB_insert_legitimization_process (
    425                      pg,
    426                      &account.h_normalized,
    427                      i,
    428                      measure_row,
    429                      (0 == i) ? "provider-a" : "provider-b",
    430                      "provider-account",
    431                      legi_id,
    432                      &process_row[i]));
    433     GNUNET_free (legi_id);
    434     GNUNET_assert (0 != process_row[i]);
    435   }
    436 }
    437 
    438 
    439 /**
    440  * Nothing is known while the table is empty.
    441  *
    442  * @param pg the database context
    443  * @return 0 on success
    444  */
    445 static int
    446 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    447 {
    448   struct AttributeContext ctx = { 0 };
    449   struct TALER_EXCHANGEDB_HistoryBuilderContext hbc;
    450   json_t *j;
    451 
    452   setup_account (pg);
    453   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    454           TALER_EXCHANGEDB_iterate_kyc_attributes (pg,
    455                                                    &account.h_normalized,
    456                                                    &attribute_cb,
    457                                                    &ctx));
    458   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    459           TALER_EXCHANGEDB_iterate_unscreened_kyc_attributes (
    460             pg,
    461             &all_attributes_cb,
    462             &ctx));
    463   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    464           TALER_EXCHANGEDB_iterate_aml_attributes (pg,
    465                                                    &account.h_normalized,
    466                                                    0,
    467                                                    16,
    468                                                    &aml_attribute_cb,
    469                                                    &ctx));
    470   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    471           TALER_EXCHANGEDB_iterate_kyc_history (pg,
    472                                                 &account.h_normalized,
    473                                                 &kyc_history_cb,
    474                                                 &ctx));
    475   FAILIF (0 != ctx.total);
    476 
    477   hbc.account = &account.h_normalized;
    478   hbc.pg = pg;
    479   hbc.attribute_key = &attribute_key;
    480   hbc.is_wallet = false;
    481   j = TALER_EXCHANGEDB_kyc_history_builder (&hbc);
    482   FAILIF (NULL == j);
    483   FAILIF_C (0 != json_array_size (j),
    484             json_decref (j));
    485   json_decref (j);
    486   /* with no attributes at all, the "current" ones are an empty object */
    487   j = TALER_EXCHANGEDB_current_attributes_builder (&hbc);
    488   FAILIF (NULL == j);
    489   FAILIF_C (! json_is_object (j),
    490             json_decref (j));
    491   FAILIF_C (0 != json_object_size (j),
    492             json_decref (j));
    493   json_decref (j);
    494   return 0;
    495 }
    496 
    497 
    498 /**
    499  * Storing attributes creates the row and finishes the KYC process they
    500  * were collected by.
    501  *
    502  * @param pg the database context
    503  * @return 0 on success
    504  */
    505 static int
    506 check_insert (struct TALER_EXCHANGEDB_PostgresContext *pg)
    507 {
    508   void *enc;
    509   size_t enc_size;
    510   enum GNUNET_DB_QueryStatus qs;
    511 
    512   encrypt_attributes ("Alice",
    513                       &enc,
    514                       &enc_size);
    515   qs = TALER_EXCHANGEDB_do_insert_kyc_attributes (
    516     pg,
    517     process_row[0],
    518     &account.h_normalized,
    519     "provider-a",
    520     "provider-account",
    521     "legi-0",
    522     0,
    523     GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS),
    524     "test-form",
    525     enc_size,
    526     enc);
    527   GNUNET_free (enc);
    528   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
    529   FAILIF (1 != TDB_count (pg,
    530                           "FROM kyc_attributes"));
    531   FAILIF (1 != TDB_count (pg,
    532                           "FROM kyc_attributes"
    533                           " WHERE NOT by_aml_officer"
    534                           "   AND form_name='test-form'"));
    535   /* new attributes still have to be screened against the sanctions list */
    536   FAILIF (1 != TDB_count (pg,
    537                           "FROM kyc_attributes"
    538                           " WHERE NOT screened"));
    539   /* the KYC process the attributes came from is now finished */
    540   FAILIF (1 != TDB_count (pg,
    541                           "FROM legitimization_processes"
    542                           " WHERE legitimization_process_serial_id=%llu"
    543                           "   AND finished",
    544                           (unsigned long long) process_row[0]));
    545   /* ... and the aggregator was told to have another look */
    546   FAILIF (0 == TDB_count (pg,
    547                           "FROM kyc_alerts"));
    548   return 0;
    549 }
    550 
    551 
    552 /**
    553  * Naming a provider that does not own the KYC process stores the
    554  * attributes but leaves the process unfinished.
    555  *
    556  * @param pg the database context
    557  * @return 0 on success
    558  */
    559 static int
    560 check_insert_wrong_provider (struct TALER_EXCHANGEDB_PostgresContext *pg)
    561 {
    562   void *enc;
    563   size_t enc_size;
    564   enum GNUNET_DB_QueryStatus qs;
    565 
    566   encrypt_attributes ("Bob",
    567                       &enc,
    568                       &enc_size);
    569   qs = TALER_EXCHANGEDB_do_insert_kyc_attributes (
    570     pg,
    571     process_row[1],
    572     &account.h_normalized,
    573     /* process_row[1] belongs to provider-b */
    574     "provider-a",
    575     "provider-account",
    576     "legi-1",
    577     0,
    578     GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS),
    579     NULL,
    580     enc_size,
    581     enc);
    582   GNUNET_free (enc);
    583   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
    584   FAILIF (2 != TDB_count (pg,
    585                           "FROM kyc_attributes"));
    586   /* the attributes were stored, but the process was not finished; the
    587      stored procedure reports that through out_ok, which the C wrapper
    588      drops on the floor (EDBT-16) */
    589   FAILIF (0 != TDB_count (pg,
    590                           "FROM legitimization_processes"
    591                           " WHERE legitimization_process_serial_id=%llu"
    592                           "   AND finished",
    593                           (unsigned long long) process_row[1]));
    594   FAILIF (1 != TDB_count (pg,
    595                           "FROM kyc_attributes"
    596                           " WHERE form_name IS NULL"));
    597   return 0;
    598 }
    599 
    600 
    601 /**
    602  * The per-account and the global iterator both find the stored rows.
    603  *
    604  * @param pg the database context
    605  * @return 0 on success
    606  */
    607 static int
    608 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
    609 {
    610   struct AttributeContext ctx;
    611   struct TALER_NormalizedPaytoHashP other;
    612 
    613   memset (&ctx,
    614           0,
    615           sizeof (ctx));
    616   FAILIF (0 >=
    617           TALER_EXCHANGEDB_iterate_kyc_attributes (pg,
    618                                                    &account.h_normalized,
    619                                                    &attribute_cb,
    620                                                    &ctx));
    621   FAILIF_C (2 != ctx.total,
    622             ac_free (&ctx));
    623   FAILIF_C (NULL == ctx.name,
    624             ac_free (&ctx));
    625   ac_free (&ctx);
    626 
    627   /* an account without attributes has none */
    628   TDB_FILL (other,
    629             99);
    630   memset (&ctx,
    631           0,
    632           sizeof (ctx));
    633   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    634           TALER_EXCHANGEDB_iterate_kyc_attributes (pg,
    635                                                    &other,
    636                                                    &attribute_cb,
    637                                                    &ctx));
    638   FAILIF_C (0 != ctx.total,
    639             ac_free (&ctx));
    640   ac_free (&ctx);
    641 
    642   /* the unscreened iterator sees both rows, in ascending row order */
    643   memset (&ctx,
    644           0,
    645           sizeof (ctx));
    646   FAILIF (0 >=
    647           TALER_EXCHANGEDB_iterate_unscreened_kyc_attributes (
    648             pg,
    649             &all_attributes_cb,
    650             &ctx));
    651   FAILIF_C (2 != ctx.total,
    652             ac_free (&ctx));
    653   FAILIF_C (0 != strcmp (ctx.name,
    654                          "Bob"),
    655             ac_free (&ctx));
    656   {
    657     uint64_t first = ctx.row_id - 1;
    658 
    659     ac_free (&ctx);
    660     /* ... but no longer the first row once that one is screened */
    661     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    662             TALER_EXCHANGEDB_update_to_kyc_attributes_screened (
    663               pg,
    664               &account.h_normalized,
    665               first));
    666     memset (&ctx,
    667             0,
    668             sizeof (ctx));
    669     FAILIF (0 >=
    670             TALER_EXCHANGEDB_iterate_unscreened_kyc_attributes (
    671               pg,
    672               &all_attributes_cb,
    673               &ctx));
    674     FAILIF_C (1 != ctx.total,
    675               ac_free (&ctx));
    676     FAILIF_C (first + 1 != ctx.row_id,
    677               ac_free (&ctx));
    678     ac_free (&ctx);
    679     /* marking a row under another account does nothing */
    680     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    681             TALER_EXCHANGEDB_update_to_kyc_attributes_screened (
    682               pg,
    683               &other,
    684               first + 1));
    685     /* with the second row screened as well, nothing is left */
    686     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    687             TALER_EXCHANGEDB_update_to_kyc_attributes_screened (
    688               pg,
    689               &account.h_normalized,
    690               first + 1));
    691     memset (&ctx,
    692             0,
    693             sizeof (ctx));
    694     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    695             TALER_EXCHANGEDB_iterate_unscreened_kyc_attributes (
    696               pg,
    697               &all_attributes_cb,
    698               &ctx));
    699     FAILIF_C (0 != ctx.total,
    700               ac_free (&ctx));
    701     ac_free (&ctx);
    702     /* a new sanctions list makes both rows show up again */
    703     FAILIF (2 !=
    704             TALER_EXCHANGEDB_update_to_kyc_attributes_unscreened (pg));
    705     FAILIF (2 != TDB_count (pg,
    706                             "FROM kyc_attributes"
    707                             " WHERE NOT screened"));
    708     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    709             TALER_EXCHANGEDB_update_to_kyc_attributes_unscreened (pg));
    710   }
    711 
    712   /* a callback that says stop is not called again */
    713   memset (&ctx,
    714           0,
    715           sizeof (ctx));
    716   ctx.stop_after = 1;
    717   FAILIF (0 >=
    718           TALER_EXCHANGEDB_iterate_unscreened_kyc_attributes (
    719             pg,
    720             &all_attributes_cb,
    721             &ctx));
    722   FAILIF_C (1 != ctx.total,
    723             ac_free (&ctx));
    724   FAILIF_C (0 != strcmp (ctx.name,
    725                          "Alice"),
    726             ac_free (&ctx));
    727   ac_free (&ctx);
    728   return 0;
    729 }
    730 
    731 
    732 /**
    733  * The KYC history reports the state of the process each set of
    734  * attributes came from.
    735  *
    736  * @param pg the database context
    737  * @return 0 on success
    738  */
    739 static int
    740 check_kyc_history (struct TALER_EXCHANGEDB_PostgresContext *pg)
    741 {
    742   struct AttributeContext ctx;
    743   struct TALER_EXCHANGEDB_HistoryBuilderContext hbc = {
    744     .account = &account.h_normalized,
    745     .pg = pg,
    746     .attribute_key = &attribute_key,
    747     .is_wallet = false
    748   };
    749   json_t *j;
    750 
    751   memset (&ctx,
    752           0,
    753           sizeof (ctx));
    754   FAILIF (0 >=
    755           TALER_EXCHANGEDB_iterate_kyc_history (pg,
    756                                                 &account.h_normalized,
    757                                                 &kyc_history_cb,
    758                                                 &ctx));
    759   FAILIF_C (2 != ctx.total,
    760             ac_free (&ctx));
    761   ac_free (&ctx);
    762 
    763   j = TALER_EXCHANGEDB_kyc_history_builder (&hbc);
    764   FAILIF (NULL == j);
    765   FAILIF_C (2 != json_array_size (j),
    766             json_decref (j));
    767   {
    768     json_t *e = json_array_get (j,
    769                                 0);
    770     const json_t *attrs = json_object_get (e,
    771                                            "attributes");
    772 
    773     FAILIF_C (NULL == attrs,
    774               json_decref (j));
    775     FAILIF_C (NULL == json_object_get (attrs,
    776                                        "NAME"),
    777               json_decref (j));
    778   }
    779   json_decref (j);
    780   return 0;
    781 }
    782 
    783 
    784 /**
    785  * Attributes filed by an AML officer are reported as such, together with
    786  * the officer's name.
    787  *
    788  * @param pg the database context
    789  * @return 0 on success
    790  */
    791 static int
    792 check_aml_attributes (struct TALER_EXCHANGEDB_PostgresContext *pg)
    793 {
    794   struct TALER_AmlOfficerPublicKeyP officer_pub;
    795   struct TALER_AmlOfficerSignatureP officer_sig;
    796   struct TALER_MasterSignatureP master_sig;
    797   struct GNUNET_TIME_Timestamp previous_change;
    798   struct TALER_FullPayto null_payto = { NULL };
    799   struct GNUNET_HashCode attributes_hash;
    800   const char *no_events[1] = { NULL };
    801   struct AttributeContext ctx;
    802   json_t *new_rules;
    803   void *enc;
    804   size_t enc_size;
    805   bool invalid_officer;
    806   bool unknown_account;
    807   bool is_wallet;
    808   uint64_t lms;
    809   struct GNUNET_TIME_Timestamp last_date;
    810   enum GNUNET_DB_QueryStatus qs;
    811 
    812   TDB_FILL (officer_pub,
    813             20);
    814   TDB_FILL (officer_sig,
    815             21);
    816   TDB_FILL (master_sig,
    817             22);
    818   TDB_FILL (attributes_hash,
    819             23);
    820   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    821           TALER_EXCHANGEDB_insert_aml_officer (pg,
    822                                                &officer_pub,
    823                                                &master_sig,
    824                                                OFFICER_NAME,
    825                                                true,
    826                                                false,
    827                                                GNUNET_TIME_timestamp_get (),
    828                                                &previous_change));
    829   encrypt_attributes ("Carol",
    830                       &enc,
    831                       &enc_size);
    832   new_rules = GNUNET_JSON_PACK (
    833     GNUNET_JSON_pack_array_steal ("rules",
    834                                   json_array ()));
    835   GNUNET_assert (NULL != new_rules);
    836   qs = TALER_EXCHANGEDB_insert_aml_decision (
    837     pg,
    838     null_payto,
    839     &account.h_normalized,
    840     GNUNET_TIME_timestamp_get (),
    841     GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
    842     NULL,
    843     new_rules,
    844     false,
    845     NULL,
    846     NULL,
    847     "collected in person",
    848     &officer_pub,
    849     &officer_sig,
    850     0,
    851     no_events,
    852     "test-form",
    853     enc_size,
    854     enc,
    855     &attributes_hash,
    856     GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_DAYS),
    857     &invalid_officer,
    858     &unknown_account,
    859     &last_date,
    860     &lms,
    861     &is_wallet);
    862   json_decref (new_rules);
    863   GNUNET_free (enc);
    864   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
    865   FAILIF (invalid_officer);
    866   FAILIF (3 != TDB_count (pg,
    867                           "FROM kyc_attributes"));
    868   FAILIF (1 != TDB_count (pg,
    869                           "FROM kyc_attributes"
    870                           " WHERE by_aml_officer"));
    871 
    872   /* ascending from the start: all three rows, the officer's last */
    873   memset (&ctx,
    874           0,
    875           sizeof (ctx));
    876   FAILIF (0 >=
    877           TALER_EXCHANGEDB_iterate_aml_attributes (pg,
    878                                                    &account.h_normalized,
    879                                                    0,
    880                                                    16,
    881                                                    &aml_attribute_cb,
    882                                                    &ctx));
    883   FAILIF_C (3 != ctx.total,
    884             ac_free (&ctx));
    885   FAILIF_C (! ctx.by_aml_officer,
    886             ac_free (&ctx));
    887   FAILIF_C (NULL == ctx.officer,
    888             ac_free (&ctx));
    889   FAILIF_C (0 != strcmp (ctx.officer,
    890                          OFFICER_NAME),
    891             ac_free (&ctx));
    892   FAILIF_C (0 != strcmp (ctx.name,
    893                          "Carol"),
    894             ac_free (&ctx));
    895   ac_free (&ctx);
    896 
    897   /* descending from the end: only the newest row */
    898   memset (&ctx,
    899           0,
    900           sizeof (ctx));
    901   FAILIF (0 >=
    902           TALER_EXCHANGEDB_iterate_aml_attributes (pg,
    903                                                    &account.h_normalized,
    904                                                    INT64_MAX,
    905                                                    -1,
    906                                                    &aml_attribute_cb,
    907                                                    &ctx));
    908   FAILIF_C (1 != ctx.total,
    909             ac_free (&ctx));
    910   FAILIF_C (! ctx.by_aml_officer,
    911             ac_free (&ctx));
    912   ac_free (&ctx);
    913 
    914   /* ascending past the last row: nothing */
    915   memset (&ctx,
    916           0,
    917           sizeof (ctx));
    918   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    919           TALER_EXCHANGEDB_iterate_aml_attributes (pg,
    920                                                    &account.h_normalized,
    921                                                    INT64_MAX,
    922                                                    16,
    923                                                    &aml_attribute_cb,
    924                                                    &ctx));
    925   FAILIF_C (0 != ctx.total,
    926             ac_free (&ctx));
    927   ac_free (&ctx);
    928   return 0;
    929 }
    930 
    931 
    932 /**
    933  * The "current attributes" builder returns the newest set, decrypted.
    934  *
    935  * @param pg the database context
    936  * @return 0 on success
    937  */
    938 static int
    939 check_current_attributes (struct TALER_EXCHANGEDB_PostgresContext *pg)
    940 {
    941   struct TALER_EXCHANGEDB_HistoryBuilderContext hbc = {
    942     .account = &account.h_normalized,
    943     .pg = pg,
    944     .attribute_key = &attribute_key,
    945     .is_wallet = false
    946   };
    947   json_t *j;
    948   const char *name;
    949 
    950   j = TALER_EXCHANGEDB_current_attributes_builder (&hbc);
    951   FAILIF (NULL == j);
    952   name = json_string_value (json_object_get (j,
    953                                              "NAME"));
    954   FAILIF_C (NULL == name,
    955             json_decref (j));
    956   FAILIF_C (0 != strcmp (name,
    957                          "Carol"),
    958             json_decref (j));
    959   json_decref (j);
    960   return 0;
    961 }
    962 
    963 
    964 /**
    965  * The checks to run, in order.
    966  */
    967 static const struct TDB_Test tests[] = {
    968   { "kyc-attributes-empty",
    969     &check_empty },
    970   { "kyc-attributes-insert",
    971     &check_insert },
    972   { "kyc-attributes-insert-wrong-provider",
    973     &check_insert_wrong_provider },
    974   { "kyc-attributes-iterate",
    975     &check_iterate },
    976   { "kyc-attributes-kyc-history",
    977     &check_kyc_history },
    978   { "kyc-attributes-aml-attributes",
    979     &check_aml_attributes },
    980   { "kyc-attributes-current-attributes",
    981     &check_current_attributes },
    982   { NULL, NULL }
    983 };
    984 
    985 
    986 int
    987 main (int argc,
    988       char *const *argv)
    989 {
    990   int ret;
    991 
    992   ret = TDB_main (argc,
    993                   argv,
    994                   "test-kyc-attributes",
    995                   "Tests for the exchangedb `kyc_attributes' table",
    996                   tests);
    997   TDB_account_free (&account);
    998   return ret;
    999 }
   1000 
   1001 
   1002 /* end of test_kyc_attributes.c */