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


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