test_kyc_events.c (12742B)
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_events.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `kyc_events` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_iterate_aml_statistics(). 23 * 24 * `kyc_events` is a plain counter log: AML decisions may name events, 25 * each of which becomes one row, and the statistics query groups those 26 * rows by name over a time range. Two of the names are special -- 27 * ACCOUNT_OPEN and ACCOUNT_IDLE also move the account's open/close time 28 * in `kyc_targets` -- so those are checked as well. 29 */ 30 #include "test_common.h" 31 #include "exchange-database/insert_aml_decision.h" 32 #include "exchange-database/insert_aml_officer.h" 33 #include "exchange-database/iterate_aml_statistics.h" 34 35 36 /** 37 * One second, in microseconds. 38 */ 39 #define SECOND 1000000LLU 40 41 42 /** 43 * Times at which we take our three decisions, in seconds since the 44 * epoch. 45 */ 46 #define T1 1600000000 47 #define T2 1600003600 48 #define T3 1600007200 49 50 51 /** 52 * Account the decisions are about. 53 */ 54 static struct TDB_Account account; 55 56 57 /** 58 * Officer taking the decisions. 59 */ 60 static struct TALER_AmlOfficerPublicKeyP officer_pub; 61 62 63 /** 64 * Build a timestamp from a number of seconds since the epoch. 65 * 66 * @param secs seconds since the epoch 67 * @return the timestamp 68 */ 69 static struct GNUNET_TIME_Timestamp 70 ts (uint64_t secs) 71 { 72 struct GNUNET_TIME_Absolute abs = { 73 .abs_value_us = secs * SECOND 74 }; 75 76 return GNUNET_TIME_absolute_to_timestamp (abs); 77 } 78 79 80 /** 81 * Closure for #statistics_cb(). 82 */ 83 struct StatsContext 84 { 85 /** 86 * How many counters did the callback see? 87 */ 88 unsigned int total; 89 90 /** 91 * Name we are interested in. 92 */ 93 const char *name; 94 95 /** 96 * Count reported for @e name, UINT64_MAX if it was not reported. 97 */ 98 uint64_t cnt; 99 }; 100 101 102 /** 103 * Callback for #TALER_EXCHANGEDB_iterate_aml_statistics(). 104 * 105 * @param cls a `struct StatsContext *` 106 * @param name name of the counter 107 * @param cnt value of the counter 108 */ 109 static void 110 statistics_cb (void *cls, 111 const char *name, 112 uint64_t cnt) 113 { 114 struct StatsContext *ctx = cls; 115 116 ctx->total++; 117 if ( (NULL != ctx->name) && 118 (0 == strcmp (name, 119 ctx->name)) ) 120 ctx->cnt = cnt; 121 } 122 123 124 /** 125 * Take an AML decision that triggers @a events. 126 * 127 * @param pg the database context 128 * @param seed seed for the officer's signature 129 * @param when when the decision is taken, in seconds since the epoch 130 * @param num_events length of @a events 131 * @param events the events to trigger 132 * @return transaction status 133 */ 134 static enum GNUNET_DB_QueryStatus 135 decide (struct TALER_EXCHANGEDB_PostgresContext *pg, 136 uint32_t seed, 137 uint64_t when, 138 size_t num_events, 139 const char *events[static num_events]) 140 { 141 struct TALER_AmlOfficerSignatureP decider_sig; 142 struct TALER_FullPayto null_payto = { NULL }; 143 struct GNUNET_TIME_Timestamp last_date; 144 json_t *new_rules; 145 bool invalid_officer; 146 bool unknown_account; 147 bool is_wallet; 148 uint64_t lms; 149 enum GNUNET_DB_QueryStatus qs; 150 151 TDB_fill (&decider_sig, 152 sizeof (decider_sig), 153 seed); 154 new_rules = GNUNET_JSON_PACK ( 155 GNUNET_JSON_pack_array_steal ("rules", 156 json_array ())); 157 GNUNET_assert (NULL != new_rules); 158 qs = TALER_EXCHANGEDB_insert_aml_decision ( 159 pg, 160 null_payto, 161 &account.h_normalized, 162 ts (when), 163 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS), 164 NULL, 165 new_rules, 166 false, 167 NULL, 168 NULL, 169 "statistics", 170 &officer_pub, 171 &decider_sig, 172 num_events, 173 events, 174 NULL, 175 0, 176 NULL, 177 NULL, 178 GNUNET_TIME_UNIT_ZERO_TS, 179 &invalid_officer, 180 &unknown_account, 181 &last_date, 182 &lms, 183 &is_wallet); 184 json_decref (new_rules); 185 GNUNET_assert (! invalid_officer); 186 return qs; 187 } 188 189 190 /** 191 * No counters while the table is empty. 192 * 193 * @param pg the database context 194 * @return 0 on success 195 */ 196 static int 197 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 198 { 199 const char *names[] = { "ACCOUNT_OPEN", "sar-filed" }; 200 struct TALER_MasterSignatureP master_sig; 201 struct GNUNET_TIME_Timestamp previous_change; 202 struct StatsContext ctx = { 0 }; 203 204 TDB_account (pg, 205 10, 206 &account); 207 TDB_FILL (officer_pub, 208 20); 209 TDB_FILL (master_sig, 210 21); 211 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 212 TALER_EXCHANGEDB_insert_aml_officer (pg, 213 &officer_pub, 214 &master_sig, 215 "Alex Officer", 216 true, 217 false, 218 ts (T1 - 1), 219 &previous_change)); 220 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 221 TALER_EXCHANGEDB_iterate_aml_statistics (pg, 222 2, 223 names, 224 ts (0), 225 ts (T3 + 1), 226 &statistics_cb, 227 &ctx)); 228 FAILIF (0 != ctx.total); 229 return 0; 230 } 231 232 233 /** 234 * Decisions record their events, and the two account-lifecycle events 235 * also move the account's open and close time. 236 * 237 * @param pg the database context 238 * @return 0 on success 239 */ 240 static int 241 check_record (struct TALER_EXCHANGEDB_PostgresContext *pg) 242 { 243 const char *open_and_sar[] = { "ACCOUNT_OPEN", "sar-filed" }; 244 const char *sar[] = { "sar-filed" }; 245 const char *idle[] = { "ACCOUNT_IDLE" }; 246 const char *none[1] = { NULL }; 247 248 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 249 decide (pg, 250 30, 251 T1, 252 2, 253 open_and_sar)); 254 FAILIF (2 != TDB_count (pg, 255 "FROM kyc_events")); 256 FAILIF (1 != TDB_count (pg, 257 "FROM kyc_targets" 258 " WHERE open_time=%llu" 259 " AND close_time IS NULL", 260 (unsigned long long) (T1 * SECOND))); 261 262 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 263 decide (pg, 264 31, 265 T2, 266 1, 267 sar)); 268 FAILIF (3 != TDB_count (pg, 269 "FROM kyc_events")); 270 271 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 272 decide (pg, 273 32, 274 T3, 275 1, 276 idle)); 277 FAILIF (4 != TDB_count (pg, 278 "FROM kyc_events")); 279 FAILIF (1 != TDB_count (pg, 280 "FROM kyc_targets" 281 " WHERE close_time=%llu", 282 (unsigned long long) (T3 * SECOND))); 283 284 /* a decision without events records none */ 285 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 286 decide (pg, 287 33, 288 T3 + 1, 289 0, 290 none)); 291 FAILIF (4 != TDB_count (pg, 292 "FROM kyc_events")); 293 return 0; 294 } 295 296 297 /** 298 * The statistics group the events by name over the requested range. 299 * 300 * @param pg the database context 301 * @return 0 on success 302 */ 303 static int 304 check_statistics (struct TALER_EXCHANGEDB_PostgresContext *pg) 305 { 306 const char *all[] = { "ACCOUNT_OPEN", "ACCOUNT_IDLE", "sar-filed" }; 307 const char *sar[] = { "sar-filed" }; 308 const char *unknown[] = { "no-such-event" }; 309 struct StatsContext ctx; 310 311 /* over the whole range every requested name that occurred is 312 reported, once */ 313 memset (&ctx, 314 0, 315 sizeof (ctx)); 316 ctx.name = "sar-filed"; 317 ctx.cnt = UINT64_MAX; 318 FAILIF (0 >= 319 TALER_EXCHANGEDB_iterate_aml_statistics (pg, 320 3, 321 all, 322 ts (0), 323 ts (T3 + 2), 324 &statistics_cb, 325 &ctx)); 326 FAILIF (3 != ctx.total); 327 FAILIF (2 != ctx.cnt); 328 329 /* a name that was not asked for is not reported, even though it 330 happened */ 331 memset (&ctx, 332 0, 333 sizeof (ctx)); 334 ctx.name = "sar-filed"; 335 ctx.cnt = UINT64_MAX; 336 FAILIF (0 >= 337 TALER_EXCHANGEDB_iterate_aml_statistics (pg, 338 1, 339 sar, 340 ts (0), 341 ts (T3 + 2), 342 &statistics_cb, 343 &ctx)); 344 FAILIF (1 != ctx.total); 345 FAILIF (2 != ctx.cnt); 346 347 /* the start of the range is inclusive, the end is exclusive */ 348 memset (&ctx, 349 0, 350 sizeof (ctx)); 351 ctx.name = "sar-filed"; 352 ctx.cnt = UINT64_MAX; 353 FAILIF (0 >= 354 TALER_EXCHANGEDB_iterate_aml_statistics (pg, 355 1, 356 sar, 357 ts (T2), 358 ts (T3), 359 &statistics_cb, 360 &ctx)); 361 FAILIF (1 != ctx.total); 362 FAILIF (1 != ctx.cnt); 363 364 memset (&ctx, 365 0, 366 sizeof (ctx)); 367 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 368 TALER_EXCHANGEDB_iterate_aml_statistics (pg, 369 1, 370 sar, 371 ts (T1), 372 ts (T1), 373 &statistics_cb, 374 &ctx)); 375 FAILIF (0 != ctx.total); 376 377 /* a range after everything that happened is empty */ 378 memset (&ctx, 379 0, 380 sizeof (ctx)); 381 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 382 TALER_EXCHANGEDB_iterate_aml_statistics (pg, 383 3, 384 all, 385 ts (T3 + 2), 386 ts (T3 + 100), 387 &statistics_cb, 388 &ctx)); 389 FAILIF (0 != ctx.total); 390 391 /* and so is a name nobody ever recorded */ 392 memset (&ctx, 393 0, 394 sizeof (ctx)); 395 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 396 TALER_EXCHANGEDB_iterate_aml_statistics (pg, 397 1, 398 unknown, 399 ts (0), 400 ts (T3 + 2), 401 &statistics_cb, 402 &ctx)); 403 FAILIF (0 != ctx.total); 404 return 0; 405 } 406 407 408 /** 409 * The checks to run, in order. 410 */ 411 static const struct TDB_Test tests[] = { 412 { "kyc-events-empty", 413 &check_empty }, 414 { "kyc-events-record", 415 &check_record }, 416 { "kyc-events-statistics", 417 &check_statistics }, 418 { NULL, NULL } 419 }; 420 421 422 int 423 main (int argc, 424 char *const *argv) 425 { 426 int ret; 427 428 ret = TDB_main (argc, 429 argv, 430 "test-kyc-events", 431 "Tests for the exchangedb `kyc_events' table", 432 tests); 433 TDB_account_free (&account); 434 return ret; 435 } 436 437 438 /* end of test_kyc_events.c */