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_wire_accounts.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `wire_accounts`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_insert_wire(), #TALER_EXCHANGEDB_update_wire(),
23 : * #TALER_EXCHANGEDB_get_wire_timestamp() and
24 : * #TALER_EXCHANGEDB_iterate_wire_accounts().
25 : *
26 : * `wire_accounts` has no foreign keys -- it is the exchange's own list of
27 : * bank accounts, not the `wire_targets` list of everyone else's. The
28 : * optional columns (conversion URL, gateways, bank label) are exercised
29 : * both set and NULL, since the iterator has to hand both back.
30 : */
31 : #include "test_common.h"
32 : #include "exchange-database/insert_wire.h"
33 : #include "exchange-database/update_wire.h"
34 : #include "exchange-database/get_wire_timestamp.h"
35 : #include "exchange-database/iterate_wire_accounts.h"
36 :
37 :
38 : /**
39 : * Closure for #account_cb().
40 : */
41 : struct AccountContext
42 : {
43 : /**
44 : * Account we are looking for.
45 : */
46 : const char *payto_uri;
47 :
48 : /**
49 : * How many accounts did the callback see in total?
50 : */
51 : unsigned int total;
52 :
53 : /**
54 : * How many times did we see @e payto_uri?
55 : */
56 : unsigned int matched;
57 :
58 : /**
59 : * Set if the account was reported with data we did not store.
60 : */
61 : bool mismatch;
62 :
63 : /**
64 : * Set if the account was reported with a conversion URL.
65 : */
66 : bool have_conversion_url;
67 :
68 : /**
69 : * Set if the account was reported with a bank label.
70 : */
71 : bool have_bank_label;
72 :
73 : /**
74 : * Priority reported for the account.
75 : */
76 : int64_t priority;
77 : };
78 :
79 :
80 : /**
81 : * Callback for #TALER_EXCHANGEDB_iterate_wire_accounts().
82 : *
83 : * @param cls a `struct AccountContext *`
84 : * @param payto_uri the account
85 : * @param conversion_url conversion service, NULL if none
86 : * @param open_banking_gateway open banking gateway, NULL if none
87 : * @param prepared_transfer_url transfer gateway, NULL if none
88 : * @param debit_restrictions debit restrictions on the account
89 : * @param credit_restrictions credit restrictions on the account
90 : * @param master_sig master signature over the account
91 : * @param bank_label label for the UI, NULL if none
92 : * @param priority ordering hint for the UI
93 : */
94 : static void
95 6 : account_cb (void *cls,
96 : const struct TALER_FullPayto payto_uri,
97 : const char *conversion_url,
98 : const char *open_banking_gateway,
99 : const char *prepared_transfer_url,
100 : const json_t *debit_restrictions,
101 : const json_t *credit_restrictions,
102 : const struct TALER_MasterSignatureP *master_sig,
103 : const char *bank_label,
104 : int64_t priority)
105 : {
106 6 : struct AccountContext *ctx = cls;
107 :
108 : (void) open_banking_gateway;
109 : (void) prepared_transfer_url;
110 : (void) master_sig;
111 6 : ctx->total++;
112 6 : if (0 != strcmp (payto_uri.full_payto,
113 : ctx->payto_uri))
114 3 : return;
115 3 : ctx->matched++;
116 3 : ctx->have_conversion_url = (NULL != conversion_url);
117 3 : ctx->have_bank_label = (NULL != bank_label);
118 3 : ctx->priority = priority;
119 3 : if ( (! json_is_array (debit_restrictions)) ||
120 3 : (! json_is_array (credit_restrictions)) )
121 0 : ctx->mismatch = true;
122 : }
123 :
124 :
125 : /**
126 : * Nothing is known about an account that was never inserted, and updating
127 : * it does not create it.
128 : *
129 : * @param pg the database context
130 : * @return 0 on success
131 : */
132 : static int
133 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
134 : {
135 1 : struct TALER_FullPayto payto = {
136 : .full_payto = (char *) "payto://x-taler-bank/localhost/wa-none"
137 : };
138 : struct GNUNET_TIME_Timestamp last_date;
139 : struct TALER_MasterSignatureP master_sig;
140 1 : json_t *empty = json_array ();
141 :
142 1 : TDB_FILL (master_sig,
143 : 1);
144 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
145 : TALER_EXCHANGEDB_get_wire_timestamp (pg,
146 : payto,
147 : &last_date),
148 : json_decref (empty));
149 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
150 : TALER_EXCHANGEDB_update_wire (pg,
151 : payto,
152 : NULL,
153 : NULL,
154 : NULL,
155 : empty,
156 : empty,
157 : GNUNET_TIME_timestamp_get (),
158 : &master_sig,
159 : NULL,
160 : 0,
161 : true),
162 : json_decref (empty));
163 1 : FAILIF_C (0 != TDB_count (pg,
164 : "FROM wire_accounts"),
165 : json_decref (empty));
166 1 : json_decref (empty);
167 1 : return 0;
168 : }
169 :
170 :
171 : /**
172 : * Insert an account with all optional columns set and read it back.
173 : *
174 : * @param pg the database context
175 : * @return 0 on success
176 : */
177 : static int
178 1 : check_insert_full (struct TALER_EXCHANGEDB_PostgresContext *pg)
179 : {
180 1 : const char *uri = "payto://x-taler-bank/localhost/wa-full";
181 1 : struct TALER_FullPayto payto = {
182 : .full_payto = (char *) uri
183 : };
184 1 : struct GNUNET_TIME_Timestamp start = GNUNET_TIME_timestamp_get ();
185 : struct GNUNET_TIME_Timestamp last_date;
186 : struct TALER_MasterSignatureP master_sig;
187 : struct AccountContext ctx;
188 : json_t *restrictions;
189 :
190 1 : TDB_FILL (master_sig,
191 : 2);
192 1 : restrictions = json_pack ("[{s:s}]",
193 : "type",
194 : "deny");
195 1 : GNUNET_assert (NULL != restrictions);
196 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
197 : TALER_EXCHANGEDB_insert_wire (pg,
198 : payto,
199 : "https://conversion.example/",
200 : "https://obg.example/",
201 : "https://ptu.example/",
202 : restrictions,
203 : restrictions,
204 : start,
205 : &master_sig,
206 : "My Bank",
207 : 7),
208 : json_decref (restrictions));
209 1 : json_decref (restrictions);
210 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
211 : TALER_EXCHANGEDB_get_wire_timestamp (pg,
212 : payto,
213 : &last_date));
214 1 : FAILIF (GNUNET_TIME_timestamp_cmp (last_date,
215 : !=,
216 : start));
217 1 : memset (&ctx,
218 : 0,
219 : sizeof (ctx));
220 1 : ctx.payto_uri = uri;
221 1 : FAILIF (0 >=
222 : TALER_EXCHANGEDB_iterate_wire_accounts (pg,
223 : &account_cb,
224 : &ctx));
225 1 : FAILIF (1 != ctx.matched);
226 1 : FAILIF (ctx.mismatch);
227 1 : FAILIF (! ctx.have_conversion_url);
228 1 : FAILIF (! ctx.have_bank_label);
229 1 : FAILIF (7 != ctx.priority);
230 1 : return 0;
231 : }
232 :
233 :
234 : /**
235 : * Insert an account with every optional column left out; the iterator has
236 : * to hand the NULLs back as NULL.
237 : *
238 : * @param pg the database context
239 : * @return 0 on success
240 : */
241 : static int
242 1 : check_insert_minimal (struct TALER_EXCHANGEDB_PostgresContext *pg)
243 : {
244 1 : const char *uri = "payto://x-taler-bank/localhost/wa-min";
245 1 : struct TALER_FullPayto payto = {
246 : .full_payto = (char *) uri
247 : };
248 : struct TALER_MasterSignatureP master_sig;
249 : struct AccountContext ctx;
250 1 : json_t *empty = json_array ();
251 :
252 1 : TDB_FILL (master_sig,
253 : 3);
254 1 : GNUNET_assert (NULL != empty);
255 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
256 : TALER_EXCHANGEDB_insert_wire (pg,
257 : payto,
258 : NULL,
259 : NULL,
260 : NULL,
261 : empty,
262 : empty,
263 : GNUNET_TIME_timestamp_get (),
264 : &master_sig,
265 : NULL,
266 : -3),
267 : json_decref (empty));
268 1 : json_decref (empty);
269 1 : memset (&ctx,
270 : 0,
271 : sizeof (ctx));
272 1 : ctx.payto_uri = uri;
273 1 : FAILIF (0 >=
274 : TALER_EXCHANGEDB_iterate_wire_accounts (pg,
275 : &account_cb,
276 : &ctx));
277 1 : FAILIF (1 != ctx.matched);
278 1 : FAILIF (ctx.mismatch);
279 1 : FAILIF (ctx.have_conversion_url);
280 1 : FAILIF (ctx.have_bank_label);
281 1 : FAILIF (-3 != ctx.priority);
282 1 : FAILIF (2 != ctx.total);
283 1 : return 0;
284 : }
285 :
286 :
287 : /**
288 : * Disabling an account takes it out of the iterator but leaves the row.
289 : *
290 : * @param pg the database context
291 : * @return 0 on success
292 : */
293 : static int
294 1 : check_disable (struct TALER_EXCHANGEDB_PostgresContext *pg)
295 : {
296 1 : const char *uri = "payto://x-taler-bank/localhost/wa-min";
297 1 : struct TALER_FullPayto payto = {
298 : .full_payto = (char *) uri
299 : };
300 1 : struct GNUNET_TIME_Timestamp change = GNUNET_TIME_timestamp_get ();
301 : struct GNUNET_TIME_Timestamp last_date;
302 : struct AccountContext ctx;
303 :
304 : /* a disabled account may be stored without restrictions and signature */
305 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
306 : TALER_EXCHANGEDB_update_wire (pg,
307 : payto,
308 : NULL,
309 : NULL,
310 : NULL,
311 : NULL,
312 : NULL,
313 : change,
314 : NULL,
315 : NULL,
316 : 0,
317 : false));
318 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
319 : TALER_EXCHANGEDB_get_wire_timestamp (pg,
320 : payto,
321 : &last_date));
322 1 : FAILIF (GNUNET_TIME_timestamp_cmp (last_date,
323 : !=,
324 : change));
325 1 : memset (&ctx,
326 : 0,
327 : sizeof (ctx));
328 1 : ctx.payto_uri = uri;
329 1 : FAILIF (0 >=
330 : TALER_EXCHANGEDB_iterate_wire_accounts (pg,
331 : &account_cb,
332 : &ctx));
333 1 : FAILIF (0 != ctx.matched);
334 1 : FAILIF (1 != ctx.total);
335 1 : FAILIF (2 != TDB_count (pg,
336 : "FROM wire_accounts"));
337 :
338 : /* re-enabling brings it back, with the new details */
339 : {
340 : struct TALER_MasterSignatureP master_sig;
341 1 : json_t *empty = json_array ();
342 :
343 1 : TDB_FILL (master_sig,
344 : 4);
345 1 : GNUNET_assert (NULL != empty);
346 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
347 : TALER_EXCHANGEDB_update_wire (pg,
348 : payto,
349 : "https://conv2.example/",
350 : NULL,
351 : NULL,
352 : empty,
353 : empty,
354 : change,
355 : &master_sig,
356 : "Relabelled",
357 : 11,
358 : true),
359 : json_decref (empty));
360 1 : json_decref (empty);
361 : }
362 1 : memset (&ctx,
363 : 0,
364 : sizeof (ctx));
365 1 : ctx.payto_uri = uri;
366 1 : FAILIF (0 >=
367 : TALER_EXCHANGEDB_iterate_wire_accounts (pg,
368 : &account_cb,
369 : &ctx));
370 1 : FAILIF (1 != ctx.matched);
371 1 : FAILIF (! ctx.have_conversion_url);
372 1 : FAILIF (! ctx.have_bank_label);
373 1 : FAILIF (11 != ctx.priority);
374 1 : return 0;
375 : }
376 :
377 :
378 : /**
379 : * The checks to run, in order.
380 : */
381 : static const struct TDB_Test tests[] = {
382 : { "wire-accounts-empty",
383 : &check_empty },
384 : { "wire-accounts-insert-full",
385 : &check_insert_full },
386 : { "wire-accounts-insert-minimal",
387 : &check_insert_minimal },
388 : { "wire-accounts-disable",
389 : &check_disable },
390 : { NULL, NULL }
391 : };
392 :
393 :
394 : int
395 1 : main (int argc,
396 : char *const *argv)
397 : {
398 1 : return TDB_main (argc,
399 : argv,
400 : "test-wire-accounts",
401 : "Tests for the exchangedb `wire_accounts' table",
402 : tests);
403 : }
404 :
405 :
406 : /* end of test_wire_accounts.c */
|