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_kyc_alerts.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `kyc_alerts`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_do_drain_kyc_alert(), and that the table keeps
23 : * one alert per account and trigger type.
24 : *
25 : * `kyc_alerts` is a work queue: the stored procedures that change the KYC
26 : * state of an account append a row, and taler-exchange-aggregator drains
27 : * it one row at a time. Nothing exported writes to it directly, so the
28 : * rows here are produced through
29 : * #TALER_EXCHANGEDB_insert_aml_program_failure(), the cheapest of the
30 : * three producers.
31 : */
32 : #include "test_common.h"
33 : #include "exchange-database/do_drain_kyc_alert.h"
34 : #include "exchange-database/insert_aml_program_failure.h"
35 :
36 :
37 : /**
38 : * Trigger type the aggregator drains, and the only one any stored
39 : * procedure ever writes.
40 : */
41 : #define AGGREGATOR_TRIGGER 1
42 :
43 : /**
44 : * Trigger type of a (hypothetical) second receiver of alerts.
45 : */
46 : #define OTHER_TRIGGER 2
47 :
48 :
49 : /**
50 : * The two accounts we raise alerts for.
51 : */
52 : static struct TDB_Account account[2];
53 :
54 :
55 : /**
56 : * Nothing to drain while the queue is empty.
57 : *
58 : * @param pg the database context
59 : * @return 0 on success
60 : */
61 : static int
62 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
63 : {
64 : struct TALER_NormalizedPaytoHashP h_payto;
65 :
66 3 : for (unsigned int i = 0; i < 2; i++)
67 2 : TDB_account (pg,
68 : 10 + i,
69 : &account[i]);
70 1 : memset (&h_payto,
71 : 0,
72 : sizeof (h_payto));
73 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
74 : TALER_EXCHANGEDB_do_drain_kyc_alert (pg,
75 : AGGREGATOR_TRIGGER,
76 : &h_payto));
77 1 : return 0;
78 : }
79 :
80 :
81 : /**
82 : * A KYC state change queues exactly one alert per account.
83 : *
84 : * @param pg the database context
85 : * @return 0 on success
86 : */
87 : static int
88 1 : check_raise (struct TALER_EXCHANGEDB_PostgresContext *pg)
89 : {
90 3 : for (unsigned int i = 0; i < 2; i++)
91 2 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
92 : TALER_EXCHANGEDB_insert_aml_program_failure (
93 : pg,
94 : 0,
95 : &account[i].h_normalized,
96 : "no such AML program",
97 : TALER_EC_EXCHANGE_GENERIC_KYC_FAILED));
98 1 : FAILIF (2 != TDB_count (pg,
99 : "FROM kyc_alerts"
100 : " WHERE trigger_type=%d",
101 : AGGREGATOR_TRIGGER));
102 :
103 : /* a second state change on an account that is already queued does not
104 : queue it twice */
105 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
106 : TALER_EXCHANGEDB_insert_aml_program_failure (
107 : pg,
108 : 0,
109 : &account[0].h_normalized,
110 : "still no such AML program",
111 : TALER_EC_EXCHANGE_GENERIC_KYC_FAILED));
112 1 : FAILIF (2 != TDB_count (pg,
113 : "FROM kyc_alerts"));
114 1 : return 0;
115 : }
116 :
117 :
118 : /**
119 : * Draining returns the queued accounts one by one and removes them.
120 : *
121 : * @param pg the database context
122 : * @return 0 on success
123 : */
124 : static int
125 1 : check_drain (struct TALER_EXCHANGEDB_PostgresContext *pg)
126 : {
127 : struct TALER_NormalizedPaytoHashP first;
128 : struct TALER_NormalizedPaytoHashP second;
129 :
130 : /* nobody listens for this trigger type, so the queue looks empty */
131 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
132 : TALER_EXCHANGEDB_do_drain_kyc_alert (pg,
133 : AGGREGATOR_TRIGGER + 1,
134 : &first));
135 1 : FAILIF (2 != TDB_count (pg,
136 : "FROM kyc_alerts"));
137 :
138 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
139 : TALER_EXCHANGEDB_do_drain_kyc_alert (pg,
140 : AGGREGATOR_TRIGGER,
141 : &first));
142 1 : FAILIF (1 != TDB_count (pg,
143 : "FROM kyc_alerts"));
144 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
145 : TALER_EXCHANGEDB_do_drain_kyc_alert (pg,
146 : AGGREGATOR_TRIGGER,
147 : &second));
148 1 : FAILIF (0 != TDB_count (pg,
149 : "FROM kyc_alerts"));
150 :
151 : /* both of our accounts came back, each exactly once */
152 1 : FAILIF (0 == GNUNET_memcmp (&first,
153 : &second));
154 3 : for (unsigned int i = 0; i < 2; i++)
155 : {
156 2 : unsigned int seen = 0;
157 :
158 2 : if (0 == GNUNET_memcmp (&first,
159 : &account[i].h_normalized))
160 1 : seen++;
161 2 : if (0 == GNUNET_memcmp (&second,
162 : &account[i].h_normalized))
163 1 : seen++;
164 2 : FAILIF (1 != seen);
165 : }
166 :
167 : /* and the queue is empty again */
168 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
169 : TALER_EXCHANGEDB_do_drain_kyc_alert (pg,
170 : AGGREGATOR_TRIGGER,
171 : &first));
172 1 : return 0;
173 : }
174 :
175 :
176 : /**
177 : * An account that was drained can be queued again.
178 : *
179 : * @param pg the database context
180 : * @return 0 on success
181 : */
182 : static int
183 1 : check_requeue (struct TALER_EXCHANGEDB_PostgresContext *pg)
184 : {
185 : struct TALER_NormalizedPaytoHashP h_payto;
186 :
187 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
188 : TALER_EXCHANGEDB_insert_aml_program_failure (
189 : pg,
190 : 0,
191 : &account[1].h_normalized,
192 : "and again",
193 : TALER_EC_EXCHANGE_GENERIC_KYC_FAILED));
194 1 : FAILIF (1 != TDB_count (pg,
195 : "FROM kyc_alerts"));
196 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
197 : TALER_EXCHANGEDB_do_drain_kyc_alert (pg,
198 : AGGREGATOR_TRIGGER,
199 : &h_payto));
200 1 : FAILIF (0 != GNUNET_memcmp (&h_payto,
201 : &account[1].h_normalized));
202 1 : FAILIF (0 != TDB_count (pg,
203 : "FROM kyc_alerts"));
204 1 : return 0;
205 : }
206 :
207 :
208 : /**
209 : * Each receiver has its own queue: an account can be queued for two
210 : * receivers at once, and draining it for one leaves it queued for the
211 : * other.
212 : *
213 : * @param pg the database context
214 : * @return 0 on success
215 : */
216 : static int
217 1 : check_per_trigger (struct TALER_EXCHANGEDB_PostgresContext *pg)
218 : {
219 : struct TALER_NormalizedPaytoHashP h_payto;
220 : char *hex;
221 :
222 : /* no producer writes another trigger type yet, so do it by hand */
223 1 : hex = TDB_hex (&account[0].h_normalized,
224 : sizeof (account[0].h_normalized));
225 1 : FAILIF_C (GNUNET_OK !=
226 : TDB_exec (pg,
227 : "INSERT INTO kyc_alerts"
228 : " (h_payto,trigger_type)"
229 : " VALUES (decode('%s','hex'),%d);",
230 : hex,
231 : OTHER_TRIGGER),
232 : GNUNET_free (hex));
233 1 : GNUNET_free (hex);
234 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
235 : TALER_EXCHANGEDB_insert_aml_program_failure (
236 : pg,
237 : 0,
238 : &account[0].h_normalized,
239 : "for the aggregator",
240 : TALER_EC_EXCHANGE_GENERIC_KYC_FAILED));
241 : /* both alerts for the same account are queued */
242 1 : FAILIF (2 != TDB_count (pg,
243 : "FROM kyc_alerts"));
244 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
245 : TALER_EXCHANGEDB_do_drain_kyc_alert (pg,
246 : AGGREGATOR_TRIGGER,
247 : &h_payto));
248 1 : FAILIF (0 != GNUNET_memcmp (&h_payto,
249 : &account[0].h_normalized));
250 : /* draining it for the aggregator leaves the other receiver's alert */
251 1 : FAILIF (1 != TDB_count (pg,
252 : "FROM kyc_alerts"
253 : " WHERE trigger_type=%d",
254 : OTHER_TRIGGER));
255 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
256 : TALER_EXCHANGEDB_do_drain_kyc_alert (pg,
257 : AGGREGATOR_TRIGGER,
258 : &h_payto));
259 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
260 : TALER_EXCHANGEDB_do_drain_kyc_alert (pg,
261 : OTHER_TRIGGER,
262 : &h_payto));
263 1 : FAILIF (0 != GNUNET_memcmp (&h_payto,
264 : &account[0].h_normalized));
265 1 : FAILIF (0 != TDB_count (pg,
266 : "FROM kyc_alerts"));
267 1 : return 0;
268 : }
269 :
270 :
271 : /**
272 : * The checks to run, in order.
273 : */
274 : static const struct TDB_Test tests[] = {
275 : { "kyc-alerts-empty",
276 : &check_empty },
277 : { "kyc-alerts-raise",
278 : &check_raise },
279 : { "kyc-alerts-drain",
280 : &check_drain },
281 : { "kyc-alerts-requeue",
282 : &check_requeue },
283 : { "kyc-alerts-per-trigger",
284 : &check_per_trigger },
285 : { NULL, NULL }
286 : };
287 :
288 :
289 : int
290 1 : main (int argc,
291 : char *const *argv)
292 : {
293 : int ret;
294 :
295 1 : ret = TDB_main (argc,
296 : argv,
297 : "test-kyc-alerts",
298 : "Tests for the exchangedb `kyc_alerts' table",
299 : tests);
300 3 : for (unsigned int i = 0; i < 2; i++)
301 2 : TDB_account_free (&account[i]);
302 1 : return ret;
303 : }
304 :
305 :
306 : /* end of test_kyc_alerts.c */
|