Line data Source code
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_aml_history.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `aml_history`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_insert_aml_decision(),
23 : * #TALER_EXCHANGEDB_iterate_aml_history(),
24 : * #TALER_EXCHANGEDB_iterate_aml_history_above_serial_id(),
25 : * #TALER_EXCHANGEDB_iterate_aml_decisions() and
26 : * #TALER_EXCHANGEDB_aml_history_builder().
27 : *
28 : * `aml_history` is what an AML officer signed: one row per decision, each
29 : * pointing at the `legitimization_outcomes` row it produced. The insert
30 : * refuses an officer who may not decide and a decision older than the one
31 : * already on file, and both of those are checked here.
32 : */
33 : #include "test_common.h"
34 : #include "exchange-database/account_history.h"
35 : #include "exchange-database/insert_aml_decision.h"
36 : #include "exchange-database/insert_aml_officer.h"
37 : #include "exchange-database/iterate_aml_decisions.h"
38 : #include "exchange-database/iterate_aml_history.h"
39 : #include "exchange-database/iterate_aml_history_above_serial_id.h"
40 :
41 :
42 : /**
43 : * Account the checks decide about.
44 : */
45 : static struct TDB_Account account;
46 :
47 :
48 : /**
49 : * Officer who takes the decisions.
50 : */
51 : static struct TALER_AmlOfficerPublicKeyP officer_pub;
52 :
53 :
54 : /**
55 : * Build a timestamp from a number of seconds since the epoch.
56 : *
57 : * @param secs seconds since the epoch
58 : * @return the timestamp
59 : */
60 : static struct GNUNET_TIME_Timestamp
61 9 : ts (uint64_t secs)
62 : {
63 9 : struct GNUNET_TIME_Absolute abs = {
64 9 : .abs_value_us = secs * 1000LLU * 1000LLU
65 : };
66 :
67 9 : return GNUNET_TIME_absolute_to_timestamp (abs);
68 : }
69 :
70 :
71 : /**
72 : * Build a rule set naming @a name as its only rule.
73 : *
74 : * @param name name of the rule
75 : * @return the rule set, to be freed with json_decref()
76 : */
77 : static json_t *
78 5 : make_rules (const char *name)
79 : {
80 : json_t *j;
81 :
82 5 : j = GNUNET_JSON_PACK (
83 : GNUNET_JSON_pack_string ("rule_name",
84 : name),
85 : GNUNET_JSON_pack_array_steal ("rules",
86 : json_array ()));
87 5 : GNUNET_assert (NULL != j);
88 5 : return j;
89 : }
90 :
91 :
92 : /**
93 : * Closure for #history_cb() and #decision_cb().
94 : */
95 : struct HistoryContext
96 : {
97 : /**
98 : * How many rows did the callback see?
99 : */
100 : unsigned int total;
101 :
102 : /**
103 : * Stop after this many rows; 0 for no limit.
104 : */
105 : unsigned int stop_after;
106 :
107 : /**
108 : * Justification of the last row seen, owned by this struct.
109 : */
110 : char *justification;
111 :
112 : /**
113 : * Whether the last row asked for an investigation.
114 : */
115 : bool to_investigate;
116 :
117 : /**
118 : * Whether the last row is the active decision.
119 : */
120 : bool is_active;
121 :
122 : /**
123 : * Whether the last row named an officer.
124 : */
125 : bool have_officer;
126 :
127 : /**
128 : * Whether the last decision exposed the officer's historical name.
129 : */
130 : char *officer_name;
131 :
132 : /**
133 : * Whether the last decision linked submitted attributes.
134 : */
135 : bool have_attributes;
136 :
137 : /**
138 : * Immediate measures of the last decision, owned by this struct.
139 : */
140 : char *new_measures;
141 : };
142 :
143 :
144 : /**
145 : * Callback for #TALER_EXCHANGEDB_iterate_aml_history().
146 : *
147 : * @param cls a `struct HistoryContext *`
148 : * @param outcome_serial_id row of the outcome the decision produced
149 : * @param decision_time when the decision was taken
150 : * @param justification why it was taken
151 : * @param new_measures measures requested immediately
152 : * @param decider_pub officer who made the decision
153 : * @param decider_name officer name at decision time
154 : * @param kyc_attributes_rowid linked submitted attributes
155 : * @param decider_pub which officer took it
156 : * @param jproperties new account properties
157 : * @param jnew_rules new account rules
158 : * @param to_investigate whether staff should investigate
159 : * @param is_active whether this is the active decision
160 : */
161 : static void
162 3 : history_cb (void *cls,
163 : uint64_t outcome_serial_id,
164 : struct GNUNET_TIME_Timestamp decision_time,
165 : const char *justification,
166 : const struct TALER_AmlOfficerPublicKeyP *decider_pub,
167 : const json_t *jproperties,
168 : const json_t *jnew_rules,
169 : bool to_investigate,
170 : bool is_active)
171 : {
172 3 : struct HistoryContext *ctx = cls;
173 :
174 : (void) outcome_serial_id;
175 : (void) decision_time;
176 : (void) jproperties;
177 : (void) jnew_rules;
178 3 : ctx->total++;
179 3 : GNUNET_free (ctx->justification);
180 3 : ctx->justification = (NULL == justification)
181 : ? NULL
182 3 : : GNUNET_strdup (justification);
183 3 : ctx->to_investigate = to_investigate;
184 3 : ctx->is_active = is_active;
185 3 : ctx->have_officer = (NULL != decider_pub);
186 3 : }
187 :
188 :
189 : /**
190 : * Callback for #TALER_EXCHANGEDB_iterate_aml_decisions().
191 : *
192 : * @param cls a `struct HistoryContext *`
193 : * @param row_id row of the decision
194 : * @param justification why it was taken
195 : * @param h_payto account it is about
196 : * @param decision_time when it was taken
197 : * @param expiration_time when the rules expire
198 : * @param jproperties new account properties
199 : * @param to_investigate whether staff should investigate
200 : * @param is_active whether this is the active decision
201 : * @param is_wallet whether the account is a wallet
202 : * @param payto the account's payto URI
203 : * @param account_rules the account's rules
204 : */
205 : static void
206 5 : decision_cb (void *cls,
207 : uint64_t row_id,
208 : const char *justification,
209 : const char *new_measures,
210 : const struct TALER_AmlOfficerPublicKeyP *decider_pub,
211 : const char *decider_name,
212 : const uint64_t *kyc_attributes_rowid,
213 : const struct TALER_NormalizedPaytoHashP *h_payto,
214 : struct GNUNET_TIME_Timestamp decision_time,
215 : struct GNUNET_TIME_Absolute expiration_time,
216 : const json_t *jproperties,
217 : bool to_investigate,
218 : bool is_active,
219 : bool is_wallet,
220 : struct TALER_FullPayto payto,
221 : const json_t *account_rules)
222 : {
223 5 : struct HistoryContext *ctx = cls;
224 :
225 : (void) row_id;
226 : (void) h_payto;
227 : (void) decision_time;
228 : (void) expiration_time;
229 : (void) jproperties;
230 : (void) is_wallet;
231 : (void) payto;
232 : (void) account_rules;
233 5 : ctx->total++;
234 5 : GNUNET_free (ctx->justification);
235 5 : ctx->justification = (NULL == justification)
236 : ? NULL
237 5 : : GNUNET_strdup (justification);
238 5 : GNUNET_free (ctx->new_measures);
239 5 : ctx->new_measures = (NULL == new_measures)
240 : ? NULL
241 5 : : GNUNET_strdup (new_measures);
242 5 : ctx->to_investigate = to_investigate;
243 5 : ctx->is_active = is_active;
244 5 : ctx->have_officer = (NULL != decider_pub);
245 5 : GNUNET_free (ctx->officer_name);
246 5 : ctx->officer_name = (NULL == decider_name)
247 : ? NULL
248 5 : : GNUNET_strdup (decider_name);
249 5 : ctx->have_attributes = (NULL != kyc_attributes_rowid);
250 5 : }
251 :
252 :
253 : /**
254 : * Callback for
255 : * #TALER_EXCHANGEDB_iterate_aml_history_above_serial_id().
256 : *
257 : * @param cls a `struct HistoryContext *`
258 : * @param rowid row of the decision
259 : * @param h_payto account it is about
260 : * @param justification why it was taken
261 : * @param decider_pub officer who took it
262 : * @param decider_sig the officer's signature
263 : * @param decision_time when it was taken
264 : * @param jproperties new account properties
265 : * @param jnew_rules new account rules
266 : * @param new_measure_name measure to apply immediately
267 : * @param to_investigate whether staff should investigate
268 : * @param attributes_expiration when the attributes expire
269 : * @param h_attributes hash of the attributes
270 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
271 : */
272 : static enum GNUNET_GenericReturnValue
273 3 : serial_cb (void *cls,
274 : uint64_t rowid,
275 : const struct TALER_NormalizedPaytoHashP *h_payto,
276 : const char *justification,
277 : const struct TALER_AmlOfficerPublicKeyP *decider_pub,
278 : const struct TALER_AmlOfficerSignatureP *decider_sig,
279 : struct GNUNET_TIME_Timestamp decision_time,
280 : const json_t *jproperties,
281 : const json_t *jnew_rules,
282 : const char *new_measure_name,
283 : bool to_investigate,
284 : struct GNUNET_TIME_Timestamp attributes_expiration,
285 : const struct GNUNET_HashCode *h_attributes)
286 : {
287 3 : struct HistoryContext *ctx = cls;
288 :
289 : (void) rowid;
290 : (void) h_payto;
291 : (void) decider_sig;
292 : (void) decision_time;
293 : (void) jproperties;
294 : (void) jnew_rules;
295 : (void) new_measure_name;
296 : (void) attributes_expiration;
297 : (void) h_attributes;
298 3 : ctx->total++;
299 3 : GNUNET_free (ctx->justification);
300 3 : ctx->justification = (NULL == justification)
301 : ? NULL
302 3 : : GNUNET_strdup (justification);
303 3 : ctx->to_investigate = to_investigate;
304 3 : ctx->have_officer = (NULL != decider_pub);
305 3 : if ( (0 != ctx->stop_after) &&
306 1 : (ctx->total >= ctx->stop_after) )
307 1 : return GNUNET_SYSERR;
308 2 : return GNUNET_OK;
309 : }
310 :
311 :
312 : /**
313 : * Outcome of an AML decision.
314 : */
315 : struct DecisionStatus
316 : {
317 : /**
318 : * May the officer not decide right now?
319 : */
320 : bool invalid_officer;
321 :
322 : /**
323 : * Is the account unknown?
324 : */
325 : bool unknown_account;
326 :
327 : /**
328 : * Time of the decision that was already on file.
329 : */
330 : struct GNUNET_TIME_Timestamp last_date;
331 :
332 : /**
333 : * Row of the measures the decision put in place.
334 : */
335 : uint64_t legitimization_measure_serial_id;
336 :
337 : /**
338 : * Is the account a wallet?
339 : */
340 : bool is_wallet;
341 : };
342 :
343 :
344 : /**
345 : * Take an AML decision about our account.
346 : *
347 : * @param pg the database context
348 : * @param decider officer taking the decision, NULL for none
349 : * @param seed seed for the officer's signature
350 : * @param when when the decision is taken, in seconds since the epoch
351 : * @param justification why
352 : * @param new_measures measures to request immediately
353 : * @param add_attributes whether to link submitted attributes
354 : * @param to_investigate whether staff should investigate
355 : * @param[out] st set to the outcome
356 : * @return transaction status
357 : */
358 : static enum GNUNET_DB_QueryStatus
359 5 : decide (struct TALER_EXCHANGEDB_PostgresContext *pg,
360 : const struct TALER_AmlOfficerPublicKeyP *decider,
361 : uint32_t seed,
362 : uint64_t when,
363 : const char *justification,
364 : const char *new_measures,
365 : bool add_attributes,
366 : bool to_investigate,
367 : struct DecisionStatus *st)
368 : {
369 : struct TALER_AmlOfficerSignatureP decider_sig;
370 : struct GNUNET_HashCode attributes_hash;
371 5 : const char encrypted_attributes[] = "encrypted attributes";
372 5 : struct TALER_FullPayto null_payto = { NULL };
373 5 : json_t *new_rules = make_rules ("decided");
374 : /* the argument is declared `const char *[static 0]', so it must not
375 : be NULL even though no event is to be triggered */
376 5 : const char *no_events[1] = { NULL };
377 : enum GNUNET_DB_QueryStatus qs;
378 :
379 5 : TDB_fill (&decider_sig,
380 : sizeof (decider_sig),
381 : seed);
382 5 : TDB_fill (&attributes_hash,
383 : sizeof (attributes_hash),
384 : seed);
385 5 : memset (st,
386 : 0,
387 : sizeof (*st));
388 6 : qs = TALER_EXCHANGEDB_insert_aml_decision (
389 : pg,
390 : null_payto,
391 : &account.h_normalized,
392 : ts (when),
393 : GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
394 : NULL,
395 : new_rules,
396 : to_investigate,
397 : new_measures,
398 : NULL,
399 : justification,
400 : decider,
401 : (NULL == decider) ? NULL : &decider_sig,
402 : 0,
403 : no_events,
404 : add_attributes ? "test-form" : NULL,
405 : add_attributes ? sizeof (encrypted_attributes) : 0,
406 : add_attributes ? encrypted_attributes : NULL,
407 : add_attributes ? &attributes_hash : NULL,
408 1 : add_attributes ? ts (when + 3600) : GNUNET_TIME_UNIT_ZERO_TS,
409 : &st->invalid_officer,
410 : &st->unknown_account,
411 : &st->last_date,
412 : &st->legitimization_measure_serial_id,
413 : &st->is_wallet);
414 5 : json_decref (new_rules);
415 5 : return qs;
416 : }
417 :
418 :
419 : /**
420 : * Nothing is known while the table is empty.
421 : *
422 : * @param pg the database context
423 : * @return 0 on success
424 : */
425 : static int
426 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
427 : {
428 1 : struct HistoryContext ctx = { 0 };
429 :
430 1 : TDB_account (pg,
431 : 10,
432 : &account);
433 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
434 : TALER_EXCHANGEDB_iterate_aml_history (pg,
435 : &account.h_normalized,
436 : 0,
437 : 10,
438 : &history_cb,
439 : &ctx));
440 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
441 : TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
442 : pg,
443 : 0,
444 : &serial_cb,
445 : &ctx));
446 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
447 : TALER_EXCHANGEDB_iterate_aml_decisions (pg,
448 : &account.h_normalized,
449 : TALER_EXCHANGE_YNA_ALL,
450 : TALER_EXCHANGE_YNA_ALL,
451 : 0,
452 : 10,
453 : &decision_cb,
454 : &ctx));
455 1 : FAILIF (0 != ctx.total);
456 1 : return 0;
457 : }
458 :
459 :
460 : /**
461 : * An officer who is not on file may not decide.
462 : *
463 : * @param pg the database context
464 : * @return 0 on success
465 : */
466 : static int
467 1 : check_invalid_officer (struct TALER_EXCHANGEDB_PostgresContext *pg)
468 : {
469 : struct DecisionStatus st;
470 :
471 1 : TDB_FILL (officer_pub,
472 : 20);
473 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
474 : decide (pg,
475 : &officer_pub,
476 : 20,
477 : 1600000000,
478 : "no such officer",
479 : NULL,
480 : false,
481 : false,
482 : &st));
483 1 : FAILIF (! st.invalid_officer);
484 1 : FAILIF (0 != TDB_count (pg,
485 : "FROM aml_history"));
486 1 : return 0;
487 : }
488 :
489 :
490 : /**
491 : * An appointed officer's decision is recorded and becomes the active
492 : * outcome.
493 : *
494 : * @param pg the database context
495 : * @return 0 on success
496 : */
497 : static int
498 1 : check_decide (struct TALER_EXCHANGEDB_PostgresContext *pg)
499 : {
500 : struct TALER_MasterSignatureP master_sig;
501 : struct GNUNET_TIME_Timestamp previous_change;
502 : struct DecisionStatus st;
503 : struct HistoryContext ctx;
504 :
505 1 : TDB_FILL (master_sig,
506 : 20);
507 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
508 : TALER_EXCHANGEDB_insert_aml_officer (pg,
509 : &officer_pub,
510 : &master_sig,
511 : "Alex Officer",
512 : true,
513 : false,
514 : ts (1500000000),
515 : &previous_change));
516 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
517 : decide (pg,
518 : &officer_pub,
519 : 20,
520 : 1600000000,
521 : "looks fine",
522 : "review-a",
523 : false,
524 : false,
525 : &st));
526 1 : FAILIF (st.invalid_officer);
527 1 : FAILIF (st.unknown_account);
528 1 : FAILIF (1 != TDB_count (pg,
529 : "FROM aml_history"));
530 1 : FAILIF (1 != TDB_count (pg,
531 : "FROM legitimization_outcomes"));
532 :
533 1 : memset (&ctx,
534 : 0,
535 : sizeof (ctx));
536 1 : FAILIF (0 >=
537 : TALER_EXCHANGEDB_iterate_aml_history (pg,
538 : &account.h_normalized,
539 : 0,
540 : 10,
541 : &history_cb,
542 : &ctx));
543 1 : FAILIF_C (1 != ctx.total,
544 : GNUNET_free (ctx.justification));
545 1 : FAILIF_C (0 != strcmp (ctx.justification,
546 : "looks fine"),
547 : GNUNET_free (ctx.justification));
548 1 : FAILIF_C (! ctx.is_active,
549 : GNUNET_free (ctx.justification));
550 1 : FAILIF_C (! ctx.have_officer,
551 : GNUNET_free (ctx.justification));
552 1 : GNUNET_free (ctx.justification);
553 1 : return 0;
554 : }
555 :
556 :
557 : /**
558 : * A decision older than the one on file is refused; a newer one
559 : * supersedes it.
560 : *
561 : * @param pg the database context
562 : * @return 0 on success
563 : */
564 : static int
565 1 : check_supersede (struct TALER_EXCHANGEDB_PostgresContext *pg)
566 : {
567 : struct DecisionStatus st;
568 : struct HistoryContext ctx;
569 :
570 : /* an older decision is refused (signalled by NO_RESULTS), and the
571 : decision that is already on file is reported back */
572 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
573 : decide (pg,
574 : &officer_pub,
575 : 21,
576 : 1500000000,
577 : "too late",
578 : NULL,
579 : false,
580 : false,
581 : &st));
582 1 : FAILIF (GNUNET_TIME_timestamp_cmp (st.last_date,
583 : !=,
584 : ts (1600000000)));
585 1 : FAILIF (1 != TDB_count (pg,
586 : "FROM aml_history"));
587 :
588 : /* a newer one is taken and deactivates the previous outcome */
589 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
590 : decide (pg,
591 : &officer_pub,
592 : 22,
593 : 1600003600,
594 : "investigate this",
595 : "+review-a review-b",
596 : true,
597 : true,
598 : &st));
599 1 : FAILIF (2 != TDB_count (pg,
600 : "FROM aml_history"));
601 1 : FAILIF (2 != TDB_count (pg,
602 : "FROM legitimization_outcomes"));
603 1 : FAILIF (1 != TDB_count (pg,
604 : "FROM legitimization_outcomes"
605 : " WHERE is_active"));
606 :
607 1 : memset (&ctx,
608 : 0,
609 : sizeof (ctx));
610 1 : FAILIF (0 >=
611 : TALER_EXCHANGEDB_iterate_aml_history (pg,
612 : &account.h_normalized,
613 : 0,
614 : 10,
615 : &history_cb,
616 : &ctx));
617 1 : FAILIF_C (2 != ctx.total,
618 : GNUNET_free (ctx.justification));
619 1 : GNUNET_free (ctx.justification);
620 1 : return 0;
621 : }
622 :
623 :
624 : /**
625 : * The AML officer's view filters by investigation and activity.
626 : *
627 : * @param pg the database context
628 : * @return 0 on success
629 : */
630 : static int
631 1 : check_decisions (struct TALER_EXCHANGEDB_PostgresContext *pg)
632 : {
633 : struct TALER_MasterSignatureP master_sig;
634 : struct GNUNET_TIME_Timestamp previous_change;
635 : struct HistoryContext ctx;
636 :
637 : /* A later rename must not rewrite the name shown on older decisions. */
638 1 : TDB_FILL (master_sig,
639 : 30);
640 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
641 : TALER_EXCHANGEDB_insert_aml_officer (pg,
642 : &officer_pub,
643 : &master_sig,
644 : "Alex Renamed",
645 : true,
646 : false,
647 : ts (1700000000),
648 : &previous_change));
649 :
650 1 : memset (&ctx,
651 : 0,
652 : sizeof (ctx));
653 1 : FAILIF (0 >=
654 : TALER_EXCHANGEDB_iterate_aml_decisions (pg,
655 : &account.h_normalized,
656 : TALER_EXCHANGE_YNA_ALL,
657 : TALER_EXCHANGE_YNA_ALL,
658 : 0,
659 : 10,
660 : &decision_cb,
661 : &ctx));
662 1 : FAILIF_C (2 != ctx.total,
663 : GNUNET_free (ctx.justification));
664 1 : FAILIF_C (NULL == ctx.new_measures,
665 : GNUNET_free (ctx.justification));
666 1 : FAILIF_C (0 != strcmp (ctx.new_measures,
667 : "+review-a review-b"),
668 : GNUNET_free (ctx.justification));
669 1 : FAILIF_C (NULL == ctx.officer_name,
670 : GNUNET_free (ctx.justification));
671 1 : FAILIF_C (0 != strcmp (ctx.officer_name,
672 : "Alex Officer"),
673 : GNUNET_free (ctx.justification));
674 1 : FAILIF_C (! ctx.have_officer,
675 : GNUNET_free (ctx.justification));
676 1 : FAILIF_C (! ctx.have_attributes,
677 : GNUNET_free (ctx.justification));
678 1 : GNUNET_free (ctx.justification);
679 1 : GNUNET_free (ctx.new_measures);
680 1 : GNUNET_free (ctx.officer_name);
681 :
682 : /* only the newer decision asked for an investigation */
683 1 : memset (&ctx,
684 : 0,
685 : sizeof (ctx));
686 1 : FAILIF (0 >=
687 : TALER_EXCHANGEDB_iterate_aml_decisions (pg,
688 : &account.h_normalized,
689 : TALER_EXCHANGE_YNA_YES,
690 : TALER_EXCHANGE_YNA_ALL,
691 : 0,
692 : 10,
693 : &decision_cb,
694 : &ctx));
695 1 : FAILIF_C (1 != ctx.total,
696 : GNUNET_free (ctx.justification));
697 1 : FAILIF_C (0 != strcmp (ctx.justification,
698 : "investigate this"),
699 : GNUNET_free (ctx.justification));
700 1 : GNUNET_free (ctx.justification);
701 1 : GNUNET_free (ctx.new_measures);
702 1 : GNUNET_free (ctx.officer_name);
703 :
704 : /* and only one of them is the active outcome */
705 1 : memset (&ctx,
706 : 0,
707 : sizeof (ctx));
708 1 : FAILIF (0 >=
709 : TALER_EXCHANGEDB_iterate_aml_decisions (pg,
710 : &account.h_normalized,
711 : TALER_EXCHANGE_YNA_ALL,
712 : TALER_EXCHANGE_YNA_YES,
713 : 0,
714 : 10,
715 : &decision_cb,
716 : &ctx));
717 1 : FAILIF_C (1 != ctx.total,
718 : GNUNET_free (ctx.justification));
719 1 : FAILIF_C (! ctx.is_active,
720 : GNUNET_free (ctx.justification));
721 1 : GNUNET_free (ctx.justification);
722 1 : GNUNET_free (ctx.new_measures);
723 1 : GNUNET_free (ctx.officer_name);
724 :
725 : /* an account nobody decided about has nothing */
726 : {
727 : struct TALER_NormalizedPaytoHashP other;
728 :
729 1 : TDB_FILL (other,
730 : 98);
731 1 : memset (&ctx,
732 : 0,
733 : sizeof (ctx));
734 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
735 : TALER_EXCHANGEDB_iterate_aml_decisions (pg,
736 : &other,
737 : TALER_EXCHANGE_YNA_ALL,
738 : TALER_EXCHANGE_YNA_ALL,
739 : 0,
740 : 10,
741 : &decision_cb,
742 : &ctx));
743 1 : FAILIF (0 != ctx.total);
744 : }
745 1 : return 0;
746 : }
747 :
748 :
749 : /**
750 : * The auditor's view walks the decisions by serial and honours an
751 : * aborting callback.
752 : *
753 : * @param pg the database context
754 : * @return 0 on success
755 : */
756 : static int
757 1 : check_serial (struct TALER_EXCHANGEDB_PostgresContext *pg)
758 : {
759 : struct HistoryContext ctx;
760 :
761 1 : memset (&ctx,
762 : 0,
763 : sizeof (ctx));
764 1 : FAILIF (0 >=
765 : TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
766 : pg,
767 : 0,
768 : &serial_cb,
769 : &ctx));
770 1 : FAILIF_C (2 != ctx.total,
771 : GNUNET_free (ctx.justification));
772 1 : FAILIF_C (! ctx.have_officer,
773 : GNUNET_free (ctx.justification));
774 1 : GNUNET_free (ctx.justification);
775 :
776 1 : memset (&ctx,
777 : 0,
778 : sizeof (ctx));
779 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
780 : TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
781 : pg,
782 : 1000,
783 : &serial_cb,
784 : &ctx));
785 1 : FAILIF (0 != ctx.total);
786 :
787 1 : memset (&ctx,
788 : 0,
789 : sizeof (ctx));
790 1 : ctx.stop_after = 1;
791 1 : FAILIF (0 >=
792 : TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
793 : pg,
794 : 0,
795 : &serial_cb,
796 : &ctx));
797 1 : FAILIF_C (1 != ctx.total,
798 : GNUNET_free (ctx.justification));
799 1 : GNUNET_free (ctx.justification);
800 1 : return 0;
801 : }
802 :
803 :
804 : /**
805 : * The history builder renders the account's AML history as JSON.
806 : *
807 : * @param pg the database context
808 : * @return 0 on success
809 : */
810 : static int
811 1 : check_history_builder (struct TALER_EXCHANGEDB_PostgresContext *pg)
812 : {
813 : struct TALER_AttributeEncryptionKeyP attribute_key;
814 1 : struct TALER_EXCHANGEDB_HistoryBuilderContext hbc = {
815 : .account = &account.h_normalized,
816 : .pg = pg,
817 : .attribute_key = &attribute_key,
818 : .is_wallet = false
819 : };
820 : json_t *j;
821 :
822 1 : TDB_FILL (attribute_key,
823 : 1);
824 1 : j = TALER_EXCHANGEDB_aml_history_builder (&hbc);
825 1 : FAILIF (NULL == j);
826 1 : FAILIF_C (! json_is_array (j),
827 : json_decref (j));
828 1 : FAILIF_C (2 != json_array_size (j),
829 : json_decref (j));
830 1 : json_decref (j);
831 1 : return 0;
832 : }
833 :
834 :
835 : /**
836 : * Automated outcomes have no AML officer provenance.
837 : *
838 : * @param pg the database context
839 : * @return 0 on success
840 : */
841 : static int
842 1 : check_automated_outcome (struct TALER_EXCHANGEDB_PostgresContext *pg)
843 : {
844 : struct DecisionStatus st;
845 1 : struct HistoryContext ctx = { 0 };
846 :
847 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
848 : decide (pg,
849 : NULL,
850 : 40,
851 : 1800000000,
852 : NULL,
853 : NULL,
854 : false,
855 : false,
856 : &st));
857 1 : FAILIF (0 >=
858 : TALER_EXCHANGEDB_iterate_aml_decisions (pg,
859 : &account.h_normalized,
860 : TALER_EXCHANGE_YNA_ALL,
861 : TALER_EXCHANGE_YNA_YES,
862 : 0,
863 : 10,
864 : &decision_cb,
865 : &ctx));
866 1 : FAILIF_C (1 != ctx.total,
867 : GNUNET_free (ctx.justification));
868 1 : FAILIF_C (ctx.have_officer,
869 : GNUNET_free (ctx.justification));
870 1 : FAILIF_C (NULL != ctx.officer_name,
871 : GNUNET_free (ctx.justification));
872 1 : GNUNET_free (ctx.justification);
873 1 : GNUNET_free (ctx.new_measures);
874 1 : GNUNET_free (ctx.officer_name);
875 1 : return 0;
876 : }
877 :
878 :
879 : /**
880 : * The checks to run, in order.
881 : */
882 : static const struct TDB_Test tests[] = {
883 : { "aml-history-empty",
884 : &check_empty },
885 : { "aml-history-invalid-officer",
886 : &check_invalid_officer },
887 : { "aml-history-decide",
888 : &check_decide },
889 : { "aml-history-supersede",
890 : &check_supersede },
891 : { "aml-history-decisions",
892 : &check_decisions },
893 : { "aml-history-serial",
894 : &check_serial },
895 : { "aml-history-history-builder",
896 : &check_history_builder },
897 : { "aml-history-automated-outcome",
898 : &check_automated_outcome },
899 : { NULL, NULL }
900 : };
901 :
902 :
903 : int
904 1 : main (int argc,
905 : char *const *argv)
906 : {
907 : int ret;
908 :
909 1 : ret = TDB_main (argc,
910 : argv,
911 : "test-aml-history",
912 : "Tests for the exchangedb `aml_history' table",
913 : tests);
914 1 : TDB_account_free (&account);
915 1 : return ret;
916 : }
917 :
918 :
919 : /* end of test_aml_history.c */
|