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_staff.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `aml_staff`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_insert_aml_officer(),
23 : * #TALER_EXCHANGEDB_get_aml_officer(),
24 : * #TALER_EXCHANGEDB_get_exists_aml_officer() and
25 : * #TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id().
26 : *
27 : * `aml_staff` has no foreign keys and is append-only: every status change
28 : * is a new row, and the current status is the newest one. A change dated
29 : * before the one already on file is refused, which is what makes the
30 : * "previous_change" output of the insert worth having.
31 : */
32 : #include "test_common.h"
33 : #include "exchange-database/get_aml_officer.h"
34 : #include "exchange-database/get_exists_aml_officer.h"
35 : #include "exchange-database/insert_aml_officer.h"
36 : #include "exchange-database/iterate_aml_staff_above_serial_id.h"
37 :
38 :
39 : /**
40 : * Build a timestamp from a number of seconds since the epoch.
41 : *
42 : * @param secs seconds since the epoch
43 : * @return the timestamp
44 : */
45 : static struct GNUNET_TIME_Timestamp
46 8 : ts (uint64_t secs)
47 : {
48 8 : struct GNUNET_TIME_Absolute abs = {
49 8 : .abs_value_us = secs * 1000LLU * 1000LLU
50 : };
51 :
52 8 : return GNUNET_TIME_absolute_to_timestamp (abs);
53 : }
54 :
55 :
56 : /**
57 : * Closure for #staff_cb().
58 : */
59 : struct StaffContext
60 : {
61 : /**
62 : * How many rows did the callback see?
63 : */
64 : unsigned int total;
65 :
66 : /**
67 : * Stop after this many rows; 0 for no limit.
68 : */
69 : unsigned int stop_after;
70 :
71 : /**
72 : * Officer we are looking for, NULL to match nothing.
73 : */
74 : const struct TALER_AmlOfficerPublicKeyP *decider_pub;
75 :
76 : /**
77 : * How many times did we see them?
78 : */
79 : unsigned int matched;
80 :
81 : /**
82 : * Active flag of the last matching row.
83 : */
84 : bool is_active;
85 :
86 : /**
87 : * Read-only flag of the last matching row.
88 : */
89 : bool read_only;
90 :
91 : /**
92 : * Name of the last matching row, owned by this struct.
93 : */
94 : char *name;
95 : };
96 :
97 :
98 : /**
99 : * Callback for #TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id().
100 : *
101 : * @param cls a `struct StaffContext *`
102 : * @param rowid row of the status change
103 : * @param decider_pub the officer
104 : * @param master_sig signature affirming the status
105 : * @param decider_name name of the officer
106 : * @param is_active whether they could act from then on
107 : * @param read_only whether their access was read-only
108 : * @param last_change when the change took effect
109 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
110 : */
111 : static enum GNUNET_GenericReturnValue
112 8 : staff_cb (void *cls,
113 : uint64_t rowid,
114 : const struct TALER_AmlOfficerPublicKeyP *decider_pub,
115 : const struct TALER_MasterSignatureP *master_sig,
116 : const char *decider_name,
117 : bool is_active,
118 : bool read_only,
119 : struct GNUNET_TIME_Timestamp last_change)
120 : {
121 8 : struct StaffContext *ctx = cls;
122 :
123 : (void) rowid;
124 : (void) master_sig;
125 : (void) last_change;
126 8 : ctx->total++;
127 8 : if ( (NULL != ctx->decider_pub) &&
128 7 : (0 == GNUNET_memcmp (decider_pub,
129 : ctx->decider_pub)) )
130 : {
131 4 : ctx->matched++;
132 4 : ctx->is_active = is_active;
133 4 : ctx->read_only = read_only;
134 4 : GNUNET_free (ctx->name);
135 4 : ctx->name = GNUNET_strdup (decider_name);
136 : }
137 8 : if ( (0 != ctx->stop_after) &&
138 1 : (ctx->total >= ctx->stop_after) )
139 1 : return GNUNET_SYSERR;
140 7 : return GNUNET_OK;
141 : }
142 :
143 :
144 : /**
145 : * Record a status change for an officer.
146 : *
147 : * @param pg the database context
148 : * @param seed seed for the officer's key and the master signature
149 : * @param name name of the officer
150 : * @param is_active whether they may act
151 : * @param read_only whether their access is read-only
152 : * @param when when the change takes effect, in seconds since the epoch
153 : * @param[out] decider_pub set to the officer's key
154 : * @param[out] previous_change set to the time of the previous change
155 : * @return transaction status
156 : */
157 : static enum GNUNET_DB_QueryStatus
158 5 : add_officer (struct TALER_EXCHANGEDB_PostgresContext *pg,
159 : uint32_t seed,
160 : const char *name,
161 : bool is_active,
162 : bool read_only,
163 : uint64_t when,
164 : struct TALER_AmlOfficerPublicKeyP *decider_pub,
165 : struct GNUNET_TIME_Timestamp *previous_change)
166 : {
167 : struct TALER_MasterSignatureP master_sig;
168 :
169 5 : TDB_fill (decider_pub,
170 : sizeof (*decider_pub),
171 : seed);
172 5 : TDB_FILL (master_sig,
173 : seed);
174 5 : return TALER_EXCHANGEDB_insert_aml_officer (pg,
175 : decider_pub,
176 : &master_sig,
177 : name,
178 : is_active,
179 : read_only,
180 : ts (when),
181 : previous_change);
182 : }
183 :
184 :
185 : /**
186 : * Nothing is known about an officer who was never appointed.
187 : *
188 : * @param pg the database context
189 : * @return 0 on success
190 : */
191 : static int
192 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
193 : {
194 : struct TALER_AmlOfficerPublicKeyP decider_pub;
195 : struct TALER_MasterSignatureP master_sig;
196 : struct GNUNET_TIME_Absolute last_change;
197 1 : struct StaffContext ctx = { 0 };
198 1 : char *decider_name = NULL;
199 : bool is_active;
200 : bool read_only;
201 :
202 1 : TDB_FILL (decider_pub,
203 : 1);
204 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
205 : TALER_EXCHANGEDB_get_aml_officer (pg,
206 : &decider_pub,
207 : &master_sig,
208 : &decider_name,
209 : &is_active,
210 : &read_only,
211 : &last_change));
212 1 : FAILIF (NULL != decider_name);
213 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
214 : TALER_EXCHANGEDB_get_exists_aml_officer (pg,
215 : &decider_pub,
216 : &read_only));
217 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
218 : TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
219 : 0,
220 : &staff_cb,
221 : &ctx));
222 1 : FAILIF (0 != ctx.total);
223 1 : return 0;
224 : }
225 :
226 :
227 : /**
228 : * Appointing an officer records them and makes them active.
229 : *
230 : * @param pg the database context
231 : * @return 0 on success
232 : */
233 : static int
234 1 : check_appoint (struct TALER_EXCHANGEDB_PostgresContext *pg)
235 : {
236 : struct TALER_AmlOfficerPublicKeyP decider_pub;
237 : struct TALER_MasterSignatureP master_sig;
238 : struct TALER_MasterSignatureP expect_sig;
239 : struct GNUNET_TIME_Timestamp previous_change;
240 : struct GNUNET_TIME_Absolute last_change;
241 : struct StaffContext ctx;
242 1 : char *decider_name = NULL;
243 1 : bool is_active = false;
244 1 : bool read_only = true;
245 :
246 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
247 : add_officer (pg,
248 : 10,
249 : "Alex Officer",
250 : true,
251 : false,
252 : 1600000000,
253 : &decider_pub,
254 : &previous_change));
255 1 : FAILIF (1 != TDB_count (pg,
256 : "FROM aml_staff"));
257 :
258 1 : TDB_FILL (expect_sig,
259 : 10);
260 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
261 : TALER_EXCHANGEDB_get_aml_officer (pg,
262 : &decider_pub,
263 : &master_sig,
264 : &decider_name,
265 : &is_active,
266 : &read_only,
267 : &last_change));
268 1 : FAILIF_C (0 != strcmp (decider_name,
269 : "Alex Officer"),
270 : GNUNET_free (decider_name));
271 1 : GNUNET_free (decider_name);
272 1 : FAILIF (0 != GNUNET_memcmp (&master_sig,
273 : &expect_sig));
274 1 : FAILIF (! is_active);
275 1 : FAILIF (read_only);
276 1 : FAILIF (last_change.abs_value_us !=
277 : ts (1600000000).abs_time.abs_value_us);
278 :
279 1 : read_only = true;
280 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
281 : TALER_EXCHANGEDB_get_exists_aml_officer (pg,
282 : &decider_pub,
283 : &read_only));
284 1 : FAILIF (read_only);
285 :
286 1 : memset (&ctx,
287 : 0,
288 : sizeof (ctx));
289 1 : ctx.decider_pub = &decider_pub;
290 1 : FAILIF (0 >=
291 : TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
292 : 0,
293 : &staff_cb,
294 : &ctx));
295 1 : FAILIF_C (1 != ctx.matched,
296 : GNUNET_free (ctx.name));
297 1 : FAILIF_C (0 != strcmp (ctx.name,
298 : "Alex Officer"),
299 : GNUNET_free (ctx.name));
300 1 : GNUNET_free (ctx.name);
301 1 : return 0;
302 : }
303 :
304 :
305 : /**
306 : * A later change supersedes the earlier one; an earlier one is refused.
307 : *
308 : * @param pg the database context
309 : * @return 0 on success
310 : */
311 : static int
312 1 : check_history (struct TALER_EXCHANGEDB_PostgresContext *pg)
313 : {
314 : struct TALER_AmlOfficerPublicKeyP decider_pub;
315 : struct TALER_MasterSignatureP master_sig;
316 : struct GNUNET_TIME_Timestamp previous_change;
317 : struct GNUNET_TIME_Absolute last_change;
318 : struct StaffContext ctx;
319 1 : char *decider_name = NULL;
320 1 : bool is_active = true;
321 1 : bool read_only = false;
322 :
323 : /* the officer is demoted to read-only */
324 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
325 : add_officer (pg,
326 : 10,
327 : "Alex Officer",
328 : true,
329 : true,
330 : 1600003600,
331 : &decider_pub,
332 : &previous_change));
333 1 : FAILIF (GNUNET_TIME_timestamp_cmp (previous_change,
334 : !=,
335 : ts (1600000000)));
336 1 : FAILIF (2 != TDB_count (pg,
337 : "FROM aml_staff"));
338 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
339 : TALER_EXCHANGEDB_get_aml_officer (pg,
340 : &decider_pub,
341 : &master_sig,
342 : &decider_name,
343 : &is_active,
344 : &read_only,
345 : &last_change));
346 1 : GNUNET_free (decider_name);
347 1 : FAILIF (! read_only);
348 1 : read_only = false;
349 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
350 : TALER_EXCHANGEDB_get_exists_aml_officer (pg,
351 : &decider_pub,
352 : &read_only));
353 1 : FAILIF (! read_only);
354 :
355 : /* A change dated before the current status is refused. The call still
356 : reports ONE_RESULT -- the stored procedure always returns its row --
357 : and the refusal shows in @e previous_change, which comes back as the
358 : status that stayed in force rather than as the one being replaced. */
359 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
360 : add_officer (pg,
361 : 10,
362 : "Alex Officer",
363 : true,
364 : false,
365 : 1600001800,
366 : &decider_pub,
367 : &previous_change));
368 1 : FAILIF (GNUNET_TIME_timestamp_cmp (previous_change,
369 : !=,
370 : ts (1600003600)));
371 1 : FAILIF (2 != TDB_count (pg,
372 : "FROM aml_staff"));
373 :
374 : /* both rows are that officer's history */
375 1 : memset (&ctx,
376 : 0,
377 : sizeof (ctx));
378 1 : ctx.decider_pub = &decider_pub;
379 1 : FAILIF (2 !=
380 : TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
381 : 0,
382 : &staff_cb,
383 : &ctx));
384 1 : FAILIF_C (2 != ctx.matched,
385 : GNUNET_free (ctx.name));
386 1 : GNUNET_free (ctx.name);
387 1 : return 0;
388 : }
389 :
390 :
391 : /**
392 : * A deactivated officer is no longer active.
393 : *
394 : * @param pg the database context
395 : * @return 0 on success
396 : */
397 : static int
398 1 : check_deactivate (struct TALER_EXCHANGEDB_PostgresContext *pg)
399 : {
400 : struct TALER_AmlOfficerPublicKeyP decider_pub;
401 : struct GNUNET_TIME_Timestamp previous_change;
402 1 : bool read_only = false;
403 :
404 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
405 : add_officer (pg,
406 : 10,
407 : "Alex Officer",
408 : false,
409 : false,
410 : 1600007200,
411 : &decider_pub,
412 : &previous_change));
413 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
414 : TALER_EXCHANGEDB_get_exists_aml_officer (pg,
415 : &decider_pub,
416 : &read_only));
417 1 : FAILIF (3 != TDB_count (pg,
418 : "FROM aml_staff"));
419 1 : return 0;
420 : }
421 :
422 :
423 : /**
424 : * The iterator's serial bound and abort return behave as documented.
425 : *
426 : * @param pg the database context
427 : * @return 0 on success
428 : */
429 : static int
430 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
431 : {
432 : struct TALER_AmlOfficerPublicKeyP other;
433 : struct GNUNET_TIME_Timestamp previous_change;
434 : struct StaffContext ctx;
435 :
436 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
437 : add_officer (pg,
438 : 11,
439 : "Bo Officer",
440 : true,
441 : true,
442 : 1600000000,
443 : &other,
444 : &previous_change));
445 1 : memset (&ctx,
446 : 0,
447 : sizeof (ctx));
448 1 : ctx.decider_pub = &other;
449 1 : FAILIF (4 !=
450 : TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
451 : 0,
452 : &staff_cb,
453 : &ctx));
454 1 : FAILIF_C (1 != ctx.matched,
455 : GNUNET_free (ctx.name));
456 1 : FAILIF_C (! ctx.read_only,
457 : GNUNET_free (ctx.name));
458 1 : GNUNET_free (ctx.name);
459 :
460 1 : memset (&ctx,
461 : 0,
462 : sizeof (ctx));
463 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
464 : TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
465 : 1000,
466 : &staff_cb,
467 : &ctx));
468 1 : FAILIF (0 != ctx.total);
469 :
470 : /* A callback that gives up stops the iteration. Unlike most of the
471 : other iterators, this one reports how many rows the callback was
472 : actually shown rather than how many the query matched. */
473 1 : memset (&ctx,
474 : 0,
475 : sizeof (ctx));
476 1 : ctx.stop_after = 1;
477 1 : FAILIF (1 !=
478 : TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
479 : 0,
480 : &staff_cb,
481 : &ctx));
482 1 : FAILIF (1 != ctx.total);
483 1 : return 0;
484 : }
485 :
486 :
487 : /**
488 : * The checks to run, in order.
489 : */
490 : static const struct TDB_Test tests[] = {
491 : { "aml-staff-empty",
492 : &check_empty },
493 : { "aml-staff-appoint",
494 : &check_appoint },
495 : { "aml-staff-history",
496 : &check_history },
497 : { "aml-staff-deactivate",
498 : &check_deactivate },
499 : { "aml-staff-iterate",
500 : &check_iterate },
501 : { NULL, NULL }
502 : };
503 :
504 :
505 : int
506 1 : main (int argc,
507 : char *const *argv)
508 : {
509 1 : return TDB_main (argc,
510 : argv,
511 : "test-aml-staff",
512 : "Tests for the exchangedb `aml_staff' table",
513 : tests);
514 : }
515 :
516 :
517 : /* end of test_aml_staff.c */
|