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_prewire.c
18 : * @brief tests for the exchangedb functions whose primary table is `prewire`
19 : * @author Christian Grothoff
20 : *
21 : * Covers #TALER_EXCHANGEDB_insert_prewire(),
22 : * #TALER_EXCHANGEDB_iterate_prewires(),
23 : * #TALER_EXCHANGEDB_update_to_prewire_finished() and
24 : * #TALER_EXCHANGEDB_update_to_prewire_failed().
25 : *
26 : * `prewire` has no foreign keys. The table is a work queue: the iterator
27 : * only reports rows that are neither finished nor failed, which is what the
28 : * checks below are about.
29 : */
30 : #include "test_common.h"
31 : #include "exchange-database/insert_prewire.h"
32 : #include "exchange-database/iterate_prewires.h"
33 : #include "exchange-database/iterate_prewires_below_open_shards.h"
34 : #include "exchange-database/update_to_prewire_finished.h"
35 : #include "exchange-database/update_to_prewire_failed.h"
36 :
37 :
38 : /**
39 : * Closure for #prewire_cb().
40 : */
41 : struct PrewireContext
42 : {
43 : /**
44 : * How many rows did the callback see?
45 : */
46 : unsigned int total;
47 :
48 : /**
49 : * Row of the first entry seen.
50 : */
51 : uint64_t first_row;
52 :
53 : /**
54 : * Payload of the first entry seen, owned by this struct.
55 : */
56 : char *first_buf;
57 :
58 : /**
59 : * Number of bytes in @e first_buf.
60 : */
61 : size_t first_buf_size;
62 :
63 : /**
64 : * Wire method of the first entry seen, owned by this struct.
65 : */
66 : char *first_method;
67 : };
68 :
69 :
70 : /**
71 : * Callback for #TALER_EXCHANGEDB_iterate_prewires().
72 : *
73 : * @param cls a `struct PrewireContext *`
74 : * @param rowid row of the entry
75 : * @param wire_method wire method of the entry
76 : * @param buf payload of the entry
77 : * @param buf_size number of bytes in @a buf
78 : */
79 : static void
80 7 : prewire_cb (void *cls,
81 : uint64_t rowid,
82 : const char *wire_method,
83 : const char *buf,
84 : size_t buf_size)
85 : {
86 7 : struct PrewireContext *ctx = cls;
87 :
88 7 : if (0 == ctx->total++)
89 : {
90 5 : ctx->first_row = rowid;
91 5 : ctx->first_buf = GNUNET_memdup (buf,
92 : buf_size);
93 5 : ctx->first_buf_size = buf_size;
94 5 : ctx->first_method = GNUNET_strdup (wire_method);
95 : }
96 7 : }
97 :
98 :
99 : /**
100 : * Release what #prewire_cb() allocated.
101 : *
102 : * @param[in,out] ctx context to clean up
103 : */
104 : static void
105 6 : ctx_free (struct PrewireContext *ctx)
106 : {
107 6 : GNUNET_free (ctx->first_buf);
108 6 : GNUNET_free (ctx->first_method);
109 6 : }
110 :
111 :
112 : /**
113 : * Closure for #rows_cb().
114 : */
115 : struct RowsContext
116 : {
117 : /**
118 : * Rows seen, in the order the callback saw them.
119 : */
120 : uint64_t rows[8];
121 :
122 : /**
123 : * How many rows did the callback see?
124 : */
125 : unsigned int total;
126 : };
127 :
128 :
129 : static void
130 11 : rows_cb (void *cls,
131 : uint64_t rowid,
132 : const char *wire_method,
133 : const char *buf,
134 : size_t buf_size)
135 : {
136 11 : struct RowsContext *rc = cls;
137 :
138 : (void) wire_method;
139 : (void) buf;
140 : (void) buf_size;
141 11 : if (rc->total < sizeof (rc->rows) / sizeof (rc->rows[0]))
142 11 : rc->rows[rc->total] = rowid;
143 11 : rc->total++;
144 11 : }
145 :
146 :
147 : /**
148 : * An empty queue yields nothing, and marking rows that do not exist does
149 : * nothing.
150 : *
151 : * @param pg the database context
152 : * @return 0 on success
153 : */
154 : static int
155 1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
156 : {
157 1 : struct PrewireContext ctx = { 0 };
158 :
159 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
160 : TALER_EXCHANGEDB_iterate_prewires (pg,
161 : 0,
162 : 10,
163 : &prewire_cb,
164 : &ctx));
165 1 : FAILIF (0 != ctx.total);
166 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
167 : TALER_EXCHANGEDB_update_to_prewire_finished (pg,
168 : 1));
169 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
170 : TALER_EXCHANGEDB_update_to_prewire_failed (pg,
171 : 1));
172 1 : return 0;
173 : }
174 :
175 :
176 : /**
177 : * Inserted rows come back in order, with their payload intact.
178 : *
179 : * @param pg the database context
180 : * @return 0 on success
181 : */
182 : static int
183 1 : check_insert_and_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
184 : {
185 : static const char buf1[] = "first-transfer";
186 : static const char buf2[] = "second-transfer";
187 : struct PrewireContext ctx;
188 : uint64_t row1;
189 :
190 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
191 : TALER_EXCHANGEDB_insert_prewire (pg,
192 : "x-taler-bank",
193 : buf1,
194 : sizeof (buf1)));
195 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
196 : TALER_EXCHANGEDB_insert_prewire (pg,
197 : "iban",
198 : buf2,
199 : sizeof (buf2)));
200 1 : memset (&ctx,
201 : 0,
202 : sizeof (ctx));
203 1 : FAILIF (0 >=
204 : TALER_EXCHANGEDB_iterate_prewires (pg,
205 : 0,
206 : 10,
207 : &prewire_cb,
208 : &ctx));
209 1 : FAILIF_C (2 != ctx.total,
210 : ctx_free (&ctx));
211 1 : FAILIF_C (sizeof (buf1) != ctx.first_buf_size,
212 : ctx_free (&ctx));
213 1 : FAILIF_C (0 != memcmp (ctx.first_buf,
214 : buf1,
215 : sizeof (buf1)),
216 : ctx_free (&ctx));
217 1 : FAILIF_C (0 != strcmp (ctx.first_method,
218 : "x-taler-bank"),
219 : ctx_free (&ctx));
220 1 : row1 = ctx.first_row;
221 1 : ctx_free (&ctx);
222 :
223 : /* the limit is honoured */
224 1 : memset (&ctx,
225 : 0,
226 : sizeof (ctx));
227 1 : FAILIF (0 >=
228 : TALER_EXCHANGEDB_iterate_prewires (pg,
229 : 0,
230 : 1,
231 : &prewire_cb,
232 : &ctx));
233 1 : FAILIF_C (1 != ctx.total,
234 : ctx_free (&ctx));
235 1 : ctx_free (&ctx);
236 :
237 : /* ...and so is the starting row */
238 1 : memset (&ctx,
239 : 0,
240 : sizeof (ctx));
241 1 : FAILIF (0 >=
242 : TALER_EXCHANGEDB_iterate_prewires (pg,
243 : row1 + 1,
244 : 10,
245 : &prewire_cb,
246 : &ctx));
247 1 : FAILIF_C (1 != ctx.total,
248 : ctx_free (&ctx));
249 1 : FAILIF_C (0 != strcmp (ctx.first_method,
250 : "iban"),
251 : ctx_free (&ctx));
252 1 : ctx_free (&ctx);
253 1 : return 0;
254 : }
255 :
256 :
257 : /**
258 : * Finished and failed rows drop out of the queue.
259 : *
260 : * @param pg the database context
261 : * @return 0 on success
262 : */
263 : static int
264 1 : check_finished_and_failed (struct TALER_EXCHANGEDB_PostgresContext *pg)
265 : {
266 : struct PrewireContext ctx;
267 : uint64_t row1;
268 : uint64_t row2;
269 :
270 1 : memset (&ctx,
271 : 0,
272 : sizeof (ctx));
273 1 : FAILIF (0 >=
274 : TALER_EXCHANGEDB_iterate_prewires (pg,
275 : 0,
276 : 10,
277 : &prewire_cb,
278 : &ctx));
279 1 : FAILIF_C (2 != ctx.total,
280 : ctx_free (&ctx));
281 1 : row1 = ctx.first_row;
282 1 : ctx_free (&ctx);
283 :
284 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
285 : TALER_EXCHANGEDB_update_to_prewire_finished (pg,
286 : row1));
287 1 : memset (&ctx,
288 : 0,
289 : sizeof (ctx));
290 1 : FAILIF (0 >=
291 : TALER_EXCHANGEDB_iterate_prewires (pg,
292 : 0,
293 : 10,
294 : &prewire_cb,
295 : &ctx));
296 1 : FAILIF_C (1 != ctx.total,
297 : ctx_free (&ctx));
298 1 : row2 = ctx.first_row;
299 1 : ctx_free (&ctx);
300 1 : FAILIF (row2 == row1);
301 :
302 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
303 : TALER_EXCHANGEDB_update_to_prewire_failed (pg,
304 : row2));
305 1 : memset (&ctx,
306 : 0,
307 : sizeof (ctx));
308 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
309 : TALER_EXCHANGEDB_iterate_prewires (pg,
310 : 0,
311 : 10,
312 : &prewire_cb,
313 : &ctx));
314 1 : FAILIF_C (0 != ctx.total,
315 : ctx_free (&ctx));
316 1 : ctx_free (&ctx);
317 :
318 : /* both rows are still on file, one finished and one failed */
319 1 : FAILIF (2 != TDB_count (pg,
320 : "FROM prewire"));
321 1 : FAILIF (1 != TDB_count (pg,
322 : "FROM prewire WHERE finished"));
323 1 : FAILIF (1 != TDB_count (pg,
324 : "FROM prewire WHERE failed"));
325 : /* marking again is still reported as a row touched */
326 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
327 : TALER_EXCHANGEDB_update_to_prewire_finished (pg,
328 : row1));
329 1 : return 0;
330 : }
331 :
332 :
333 : /**
334 : * Unfinished rows below every open shard of the job are found; rows in
335 : * or above an open shard, finished and failed rows, and the shards of
336 : * other jobs do not count.
337 : *
338 : * @param pg the database context
339 : * @return 0 on success
340 : */
341 : static int
342 1 : check_below_open_shards (struct TALER_EXCHANGEDB_PostgresContext *pg)
343 : {
344 : static const char buf[] = "late-transfer";
345 : struct RowsContext rc;
346 : uint64_t r[4];
347 :
348 5 : for (unsigned int i = 0; i < 4; i++)
349 4 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
350 : TALER_EXCHANGEDB_insert_prewire (pg,
351 : "iban",
352 : buf,
353 : sizeof (buf)));
354 : /* the rows of the earlier checks are finished or failed */
355 1 : memset (&rc,
356 : 0,
357 : sizeof (rc));
358 1 : FAILIF (0 >=
359 : TALER_EXCHANGEDB_iterate_prewires (pg,
360 : 0,
361 : 10,
362 : &rows_cb,
363 : &rc));
364 1 : FAILIF (4 != rc.total);
365 1 : memcpy (r,
366 : rc.rows,
367 : sizeof (r));
368 :
369 : /* without shards, nothing is below them */
370 1 : memset (&rc,
371 : 0,
372 : sizeof (rc));
373 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
374 : TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
375 : "transfer",
376 : 10,
377 : &rows_cb,
378 : &rc));
379 1 : FAILIF (0 != rc.total);
380 :
381 : /* r[0] and r[1] lie in a completed shard, r[2] and r[3] in an open
382 : one; an open shard of another job covers all of them */
383 1 : FAILIF (GNUNET_OK !=
384 : TDB_exec (pg,
385 : "INSERT INTO work_shards"
386 : " (job_name,last_attempt,start_row,end_row"
387 : " ,completed,progress_row)"
388 : " VALUES"
389 : " ('transfer',0,0,%llu,TRUE,%llu)"
390 : ",('transfer',0,%llu,%llu,FALSE,%llu)"
391 : ",('other',0,0,%llu,FALSE,0);",
392 : (unsigned long long) r[2],
393 : (unsigned long long) r[2],
394 : (unsigned long long) r[2],
395 : (unsigned long long) (r[3] + 10),
396 : (unsigned long long) (r[3] + 1),
397 : (unsigned long long) (r[3] + 10)));
398 1 : memset (&rc,
399 : 0,
400 : sizeof (rc));
401 1 : FAILIF (0 >=
402 : TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
403 : "transfer",
404 : 10,
405 : &rows_cb,
406 : &rc));
407 1 : FAILIF (2 != rc.total);
408 1 : FAILIF (r[0] != rc.rows[0]);
409 1 : FAILIF (r[1] != rc.rows[1]);
410 :
411 : /* the limit is honoured */
412 1 : memset (&rc,
413 : 0,
414 : sizeof (rc));
415 1 : FAILIF (0 >=
416 : TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
417 : "transfer",
418 : 1,
419 : &rows_cb,
420 : &rc));
421 1 : FAILIF (1 != rc.total);
422 1 : FAILIF (r[0] != rc.rows[0]);
423 :
424 : /* finished rows drop out */
425 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
426 : TALER_EXCHANGEDB_update_to_prewire_finished (pg,
427 : r[0]));
428 1 : memset (&rc,
429 : 0,
430 : sizeof (rc));
431 1 : FAILIF (0 >=
432 : TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
433 : "transfer",
434 : 10,
435 : &rows_cb,
436 : &rc));
437 1 : FAILIF (1 != rc.total);
438 1 : FAILIF (r[1] != rc.rows[0]);
439 :
440 : /* with no shard of the job open, everything below the highest shard's
441 : end counts */
442 1 : FAILIF (GNUNET_OK !=
443 : TDB_exec (pg,
444 : "UPDATE work_shards"
445 : " SET completed=TRUE"
446 : " ,progress_row=end_row"
447 : " WHERE job_name='transfer';"));
448 1 : memset (&rc,
449 : 0,
450 : sizeof (rc));
451 1 : FAILIF (0 >=
452 : TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
453 : "transfer",
454 : 10,
455 : &rows_cb,
456 : &rc));
457 1 : FAILIF (3 != rc.total);
458 1 : FAILIF (r[1] != rc.rows[0]);
459 1 : FAILIF (r[2] != rc.rows[1]);
460 1 : FAILIF (r[3] != rc.rows[2]);
461 1 : return 0;
462 : }
463 :
464 :
465 : /**
466 : * The checks to run, in order.
467 : */
468 : static const struct TDB_Test tests[] = {
469 : { "prewire-empty",
470 : &check_empty },
471 : { "prewire-insert-and-iterate",
472 : &check_insert_and_iterate },
473 : { "prewire-finished-and-failed",
474 : &check_finished_and_failed },
475 : { "prewire-below-open-shards",
476 : &check_below_open_shards },
477 : { NULL, NULL }
478 : };
479 :
480 :
481 : int
482 1 : main (int argc,
483 : char *const *argv)
484 : {
485 1 : return TDB_main (argc,
486 : argv,
487 : "test-prewire",
488 : "Tests for the exchangedb `prewire' table",
489 : tests);
490 : }
491 :
492 :
493 : /* end of test_prewire.c */
|