test_legitimization_processes.c (19210B)
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_processes.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `legitimization_processes` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_insert_legitimization_process(), 23 * #TALER_EXCHANGEDB_update_legitimization_process_by_row(), 24 * #TALER_EXCHANGEDB_insert_kyc_failure(), 25 * #TALER_EXCHANGEDB_insert_aml_program_failure(), 26 * #TALER_EXCHANGEDB_get_legitimization_process_by_account(), 27 * #TALER_EXCHANGEDB_get_pending_legitimization_process(), 28 * #TALER_EXCHANGEDB_get_kyc_provider_account() and 29 * #TALER_EXCHANGEDB_iterate_kyc_references(). 30 * 31 * A row of `legitimization_processes` is one run of one KYC provider 32 * against one account, working on one measure of a 33 * `legitimization_measures` set. The checks therefore create an account 34 * and a measure set first, and then walk a process from "started" through 35 * "redirect known" to "finished". 36 */ 37 #include "test_common.h" 38 #include "exchange-database/do_trigger_kyc_rule_for_account.h" 39 #include "exchange-database/get_kyc_provider_account.h" 40 #include "exchange-database/get_legitimization_process_by_account.h" 41 #include "exchange-database/get_pending_legitimization_process.h" 42 #include "exchange-database/insert_aml_program_failure.h" 43 #include "exchange-database/insert_kyc_failure.h" 44 #include "exchange-database/insert_legitimization_process.h" 45 #include "exchange-database/iterate_kyc_references.h" 46 #include "exchange-database/update_legitimization_process_by_row.h" 47 48 49 /** 50 * Account the checks run processes against. 51 */ 52 static struct TDB_Account account; 53 54 55 /** 56 * Measure set the processes work on. 57 */ 58 static uint64_t measure_row; 59 60 61 /** 62 * Row of the process check_start() created. 63 */ 64 static uint64_t process_row; 65 66 67 /** 68 * Closure for #reference_cb(). 69 */ 70 struct ReferenceContext 71 { 72 /** 73 * How many references did the callback see? 74 */ 75 unsigned int total; 76 77 /** 78 * Provider we are looking for, NULL to match nothing. 79 */ 80 const char *provider; 81 82 /** 83 * How many times did we see it? 84 */ 85 unsigned int matched; 86 87 /** 88 * Provider account id reported for it, owned by this struct. 89 */ 90 char *user_id; 91 92 /** 93 * Provider legitimization id reported for it, owned by this struct. 94 */ 95 char *legi_id; 96 }; 97 98 99 /** 100 * Callback for #TALER_EXCHANGEDB_iterate_kyc_references(). 101 * 102 * @param cls a `struct ReferenceContext *` 103 * @param kyc_provider_name provider that ran the process 104 * @param provider_user_id account id at the provider 105 * @param legi_id legitimization id at the provider 106 */ 107 static void 108 reference_cb (void *cls, 109 const char *kyc_provider_name, 110 const char *provider_user_id, 111 const char *legi_id) 112 { 113 struct ReferenceContext *ctx = cls; 114 115 ctx->total++; 116 if ( (NULL != ctx->provider) && 117 (0 == strcmp (kyc_provider_name, 118 ctx->provider)) ) 119 { 120 ctx->matched++; 121 GNUNET_free (ctx->user_id); 122 GNUNET_free (ctx->legi_id); 123 ctx->user_id = (NULL == provider_user_id) 124 ? NULL 125 : GNUNET_strdup (provider_user_id); 126 ctx->legi_id = (NULL == legi_id) 127 ? NULL 128 : GNUNET_strdup (legi_id); 129 } 130 } 131 132 133 /** 134 * Put a measure on our account, creating the account on the way. 135 * 136 * @param pg the database context 137 */ 138 static void 139 setup_account (struct TALER_EXCHANGEDB_PostgresContext *pg) 140 { 141 json_t *jmeasures; 142 uint64_t row = 0; 143 bool bad_kyc_auth; 144 145 if (0 != measure_row) 146 return; 147 TDB_account (pg, 148 10, 149 &account); 150 jmeasures = GNUNET_JSON_PACK ( 151 GNUNET_JSON_pack_array_steal ( 152 "measures", 153 json_pack ("[{s:s,s:s,s:s}]", 154 "check_name", 155 "verify-id", 156 "prog_name", 157 "skip", 158 "context", 159 "none")), 160 GNUNET_JSON_pack_bool ("is_and_combinator", 161 true)); 162 GNUNET_assert (NULL != jmeasures); 163 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 164 TALER_EXCHANGEDB_do_trigger_kyc_rule_for_account ( 165 pg, 166 account.payto, 167 &account.h_normalized, 168 NULL, 169 NULL, 170 jmeasures, 171 1, 172 &row, 173 &bad_kyc_auth)); 174 json_decref (jmeasures); 175 GNUNET_assert (0 != row); 176 measure_row = row; 177 } 178 179 180 /** 181 * Nothing is known while the table is empty. 182 * 183 * @param pg the database context 184 * @return 0 on success 185 */ 186 static int 187 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 188 { 189 struct TALER_NormalizedPaytoHashP h_payto; 190 struct GNUNET_TIME_Absolute expiration; 191 struct ReferenceContext ctx = { 0 }; 192 char *provider_account_id = NULL; 193 char *provider_legitimization_id = NULL; 194 char *redirect_url = NULL; 195 uint64_t row; 196 bool is_wallet; 197 198 setup_account (pg); 199 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 200 TALER_EXCHANGEDB_get_legitimization_process_by_account ( 201 pg, 202 "test-provider", 203 &account.h_normalized, 204 &row, 205 &expiration, 206 &provider_account_id, 207 &provider_legitimization_id, 208 &is_wallet)); 209 FAILIF (NULL != provider_account_id); 210 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 211 TALER_EXCHANGEDB_get_pending_legitimization_process ( 212 pg, 213 &account.h_normalized, 214 "test-provider", 215 &redirect_url)); 216 FAILIF (NULL != redirect_url); 217 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 218 TALER_EXCHANGEDB_get_kyc_provider_account (pg, 219 "test-provider", 220 "legi-1", 221 &h_payto, 222 &is_wallet, 223 &row)); 224 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 225 TALER_EXCHANGEDB_iterate_kyc_references (pg, 226 &account.h_normalized, 227 &reference_cb, 228 &ctx)); 229 FAILIF (0 != ctx.total); 230 return 0; 231 } 232 233 234 /** 235 * Starting a process records it and makes it findable by provider and by 236 * the provider's own identifiers. 237 * 238 * @param pg the database context 239 * @return 0 on success 240 */ 241 static int 242 check_start (struct TALER_EXCHANGEDB_PostgresContext *pg) 243 { 244 struct TALER_NormalizedPaytoHashP h_payto; 245 struct GNUNET_TIME_Absolute expiration; 246 struct ReferenceContext ctx; 247 char *provider_account_id = NULL; 248 char *provider_legitimization_id = NULL; 249 uint64_t row = 0; 250 bool is_wallet = true; 251 252 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 253 TALER_EXCHANGEDB_insert_legitimization_process ( 254 pg, 255 &account.h_normalized, 256 0, 257 measure_row, 258 "test-provider", 259 "provider-account-1", 260 "legi-1", 261 &process_row)); 262 FAILIF (0 == process_row); 263 FAILIF (1 != TDB_count (pg, 264 "FROM legitimization_processes")); 265 266 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 267 TALER_EXCHANGEDB_get_legitimization_process_by_account ( 268 pg, 269 "test-provider", 270 &account.h_normalized, 271 &row, 272 &expiration, 273 &provider_account_id, 274 &provider_legitimization_id, 275 &is_wallet)); 276 FAILIF (row != process_row); 277 FAILIF_C (0 != strcmp (provider_account_id, 278 "provider-account-1"), 279 GNUNET_free (provider_account_id); 280 GNUNET_free (provider_legitimization_id)); 281 FAILIF_C (0 != strcmp (provider_legitimization_id, 282 "legi-1"), 283 GNUNET_free (provider_account_id); 284 GNUNET_free (provider_legitimization_id)); 285 GNUNET_free (provider_account_id); 286 GNUNET_free (provider_legitimization_id); 287 FAILIF (is_wallet); 288 289 /* another provider has no process on this account */ 290 provider_account_id = NULL; 291 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 292 TALER_EXCHANGEDB_get_legitimization_process_by_account ( 293 pg, 294 "other-provider", 295 &account.h_normalized, 296 &row, 297 &expiration, 298 &provider_account_id, 299 &provider_legitimization_id, 300 &is_wallet)); 301 302 /* the provider's own identifier resolves back to the account */ 303 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 304 TALER_EXCHANGEDB_get_kyc_provider_account (pg, 305 "test-provider", 306 "legi-1", 307 &h_payto, 308 &is_wallet, 309 &row)); 310 FAILIF (0 != GNUNET_memcmp (&h_payto, 311 &account.h_normalized)); 312 FAILIF (row != process_row); 313 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 314 TALER_EXCHANGEDB_get_kyc_provider_account (pg, 315 "test-provider", 316 "legi-does-not-exist", 317 &h_payto, 318 &is_wallet, 319 &row)); 320 321 memset (&ctx, 322 0, 323 sizeof (ctx)); 324 ctx.provider = "test-provider"; 325 FAILIF (0 >= 326 TALER_EXCHANGEDB_iterate_kyc_references (pg, 327 &account.h_normalized, 328 &reference_cb, 329 &ctx)); 330 FAILIF_C (1 != ctx.matched, 331 GNUNET_free (ctx.user_id); GNUNET_free (ctx.legi_id)); 332 FAILIF_C (0 != strcmp (ctx.user_id, 333 "provider-account-1"), 334 GNUNET_free (ctx.user_id); GNUNET_free (ctx.legi_id)); 335 GNUNET_free (ctx.user_id); 336 GNUNET_free (ctx.legi_id); 337 return 0; 338 } 339 340 341 /** 342 * Updating a process records the redirect URL and can mark it finished. 343 * 344 * @param pg the database context 345 * @return 0 on success 346 */ 347 static int 348 check_update (struct TALER_EXCHANGEDB_PostgresContext *pg) 349 { 350 struct GNUNET_TIME_Absolute expiration 351 = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS); 352 char *redirect_url = NULL; 353 354 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 355 TALER_EXCHANGEDB_update_legitimization_process_by_row ( 356 pg, 357 process_row, 358 "test-provider", 359 &account.h_normalized, 360 "provider-account-1", 361 "legi-1", 362 "https://kyc.example/start", 363 expiration, 364 TALER_EC_NONE, 365 NULL, 366 false)); 367 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 368 TALER_EXCHANGEDB_get_pending_legitimization_process ( 369 pg, 370 &account.h_normalized, 371 "test-provider", 372 &redirect_url)); 373 FAILIF (NULL == redirect_url); 374 FAILIF_C (0 != strcmp (redirect_url, 375 "https://kyc.example/start"), 376 GNUNET_free (redirect_url)); 377 GNUNET_free (redirect_url); 378 379 /* a row that does not exist is not created */ 380 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 381 TALER_EXCHANGEDB_update_legitimization_process_by_row ( 382 pg, 383 process_row + 1000, 384 "test-provider", 385 &account.h_normalized, 386 "provider-account-1", 387 "legi-1", 388 "https://kyc.example/start", 389 expiration, 390 TALER_EC_NONE, 391 NULL, 392 false)); 393 FAILIF (1 != TDB_count (pg, 394 "FROM legitimization_processes")); 395 396 /* marking it finished takes it out of the pending view */ 397 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 398 TALER_EXCHANGEDB_update_legitimization_process_by_row ( 399 pg, 400 process_row, 401 "test-provider", 402 &account.h_normalized, 403 "provider-account-1", 404 "legi-1", 405 "https://kyc.example/start", 406 expiration, 407 TALER_EC_NONE, 408 NULL, 409 true)); 410 redirect_url = NULL; 411 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 412 TALER_EXCHANGEDB_get_pending_legitimization_process ( 413 pg, 414 &account.h_normalized, 415 "test-provider", 416 &redirect_url)); 417 FAILIF (NULL != redirect_url); 418 return 0; 419 } 420 421 422 /** 423 * A failed KYC check marks the process finished with an error. 424 * 425 * @param pg the database context 426 * @return 0 on success 427 */ 428 static int 429 check_kyc_failure (struct TALER_EXCHANGEDB_PostgresContext *pg) 430 { 431 uint64_t row = 0; 432 433 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 434 TALER_EXCHANGEDB_insert_legitimization_process ( 435 pg, 436 &account.h_normalized, 437 1, 438 measure_row, 439 "failing-provider", 440 "provider-account-2", 441 "legi-2", 442 &row)); 443 FAILIF (2 != TDB_count (pg, 444 "FROM legitimization_processes")); 445 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 446 TALER_EXCHANGEDB_insert_kyc_failure ( 447 pg, 448 row, 449 &account.h_normalized, 450 "failing-provider", 451 "provider-account-2", 452 "legi-2", 453 "the provider said no", 454 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY)); 455 FAILIF (1 != TDB_count (pg, 456 "FROM legitimization_processes" 457 " WHERE finished" 458 " AND error_code=%u", 459 (unsigned int) 460 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY)); 461 /* a row that does not exist is not created */ 462 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 463 TALER_EXCHANGEDB_insert_kyc_failure ( 464 pg, 465 row + 1000, 466 &account.h_normalized, 467 "failing-provider", 468 "provider-account-2", 469 "legi-2", 470 "the provider said no", 471 TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY)); 472 FAILIF (2 != TDB_count (pg, 473 "FROM legitimization_processes")); 474 return 0; 475 } 476 477 478 /** 479 * A failed AML program marks its process finished with an error too. 480 * 481 * @param pg the database context 482 * @return 0 on success 483 */ 484 static int 485 check_aml_program_failure (struct TALER_EXCHANGEDB_PostgresContext *pg) 486 { 487 uint64_t row = 0; 488 489 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 490 TALER_EXCHANGEDB_insert_legitimization_process ( 491 pg, 492 &account.h_normalized, 493 2, 494 measure_row, 495 "aml-provider", 496 "provider-account-3", 497 "legi-3", 498 &row)); 499 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 500 TALER_EXCHANGEDB_insert_aml_program_failure ( 501 pg, 502 row, 503 &account.h_normalized, 504 "the AML program crashed", 505 TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE)); 506 FAILIF (1 != TDB_count (pg, 507 "FROM legitimization_processes" 508 " WHERE finished" 509 " AND error_code=%u", 510 (unsigned int) 511 TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE)); 512 /* Unlike insert_kyc_failure(), a row that does not exist is *created* 513 here rather than ignored -- the stored procedure would rather keep a 514 record of the failed AML program than lose it. */ 515 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 516 TALER_EXCHANGEDB_insert_aml_program_failure ( 517 pg, 518 row + 1000, 519 &account.h_normalized, 520 "the AML program crashed", 521 TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE)); 522 FAILIF (4 != TDB_count (pg, 523 "FROM legitimization_processes")); 524 return 0; 525 } 526 527 528 /** 529 * The reference listing reports every provider that worked on the 530 * account. 531 * 532 * @param pg the database context 533 * @return 0 on success 534 */ 535 static int 536 check_references (struct TALER_EXCHANGEDB_PostgresContext *pg) 537 { 538 struct ReferenceContext ctx; 539 540 memset (&ctx, 541 0, 542 sizeof (ctx)); 543 ctx.provider = "aml-provider"; 544 FAILIF (0 >= 545 TALER_EXCHANGEDB_iterate_kyc_references (pg, 546 &account.h_normalized, 547 &reference_cb, 548 &ctx)); 549 FAILIF_C (1 != ctx.matched, 550 GNUNET_free (ctx.user_id); GNUNET_free (ctx.legi_id)); 551 FAILIF_C (4 != ctx.total, 552 GNUNET_free (ctx.user_id); GNUNET_free (ctx.legi_id)); 553 GNUNET_free (ctx.user_id); 554 GNUNET_free (ctx.legi_id); 555 556 /* an account nobody checked has no references */ 557 { 558 struct TALER_NormalizedPaytoHashP other; 559 560 TDB_FILL (other, 561 98); 562 memset (&ctx, 563 0, 564 sizeof (ctx)); 565 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 566 TALER_EXCHANGEDB_iterate_kyc_references (pg, 567 &other, 568 &reference_cb, 569 &ctx)); 570 FAILIF (0 != ctx.total); 571 } 572 return 0; 573 } 574 575 576 /** 577 * The checks to run, in order. 578 */ 579 static const struct TDB_Test tests[] = { 580 { "legitimization-processes-empty", 581 &check_empty }, 582 { "legitimization-processes-start", 583 &check_start }, 584 { "legitimization-processes-update", 585 &check_update }, 586 { "legitimization-processes-kyc-failure", 587 &check_kyc_failure }, 588 { "legitimization-processes-aml-program-failure", 589 &check_aml_program_failure }, 590 { "legitimization-processes-references", 591 &check_references }, 592 { NULL, NULL } 593 }; 594 595 596 int 597 main (int argc, 598 char *const *argv) 599 { 600 int ret; 601 602 ret = TDB_main (argc, 603 argv, 604 "test-legitimization-processes", 605 "Tests for the exchangedb `legitimization_processes' table", 606 tests); 607 TDB_account_free (&account); 608 return ret; 609 } 610 611 612 /* end of test_legitimization_processes.c */