test_prewire.c (15267B)
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 prewire_cb (void *cls, 81 uint64_t rowid, 82 const char *wire_method, 83 const char *buf, 84 size_t buf_size) 85 { 86 struct PrewireContext *ctx = cls; 87 88 if (0 == ctx->total++) 89 { 90 ctx->first_row = rowid; 91 ctx->first_buf = GNUNET_memdup (buf, 92 buf_size); 93 ctx->first_buf_size = buf_size; 94 ctx->first_method = GNUNET_strdup (wire_method); 95 } 96 } 97 98 99 /** 100 * Release what #prewire_cb() allocated. 101 * 102 * @param[in,out] ctx context to clean up 103 */ 104 static void 105 ctx_free (struct PrewireContext *ctx) 106 { 107 GNUNET_free (ctx->first_buf); 108 GNUNET_free (ctx->first_method); 109 } 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 rows_cb (void *cls, 131 uint64_t rowid, 132 const char *wire_method, 133 const char *buf, 134 size_t buf_size) 135 { 136 struct RowsContext *rc = cls; 137 138 (void) wire_method; 139 (void) buf; 140 (void) buf_size; 141 if (rc->total < sizeof (rc->rows) / sizeof (rc->rows[0])) 142 rc->rows[rc->total] = rowid; 143 rc->total++; 144 } 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 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 156 { 157 struct PrewireContext ctx = { 0 }; 158 159 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 160 TALER_EXCHANGEDB_iterate_prewires (pg, 161 0, 162 10, 163 &prewire_cb, 164 &ctx)); 165 FAILIF (0 != ctx.total); 166 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 167 TALER_EXCHANGEDB_update_to_prewire_finished (pg, 168 1)); 169 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 170 TALER_EXCHANGEDB_update_to_prewire_failed (pg, 171 1)); 172 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 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 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 191 TALER_EXCHANGEDB_insert_prewire (pg, 192 "x-taler-bank", 193 buf1, 194 sizeof (buf1))); 195 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 196 TALER_EXCHANGEDB_insert_prewire (pg, 197 "iban", 198 buf2, 199 sizeof (buf2))); 200 memset (&ctx, 201 0, 202 sizeof (ctx)); 203 FAILIF (0 >= 204 TALER_EXCHANGEDB_iterate_prewires (pg, 205 0, 206 10, 207 &prewire_cb, 208 &ctx)); 209 FAILIF_C (2 != ctx.total, 210 ctx_free (&ctx)); 211 FAILIF_C (sizeof (buf1) != ctx.first_buf_size, 212 ctx_free (&ctx)); 213 FAILIF_C (0 != memcmp (ctx.first_buf, 214 buf1, 215 sizeof (buf1)), 216 ctx_free (&ctx)); 217 FAILIF_C (0 != strcmp (ctx.first_method, 218 "x-taler-bank"), 219 ctx_free (&ctx)); 220 row1 = ctx.first_row; 221 ctx_free (&ctx); 222 223 /* the limit is honoured */ 224 memset (&ctx, 225 0, 226 sizeof (ctx)); 227 FAILIF (0 >= 228 TALER_EXCHANGEDB_iterate_prewires (pg, 229 0, 230 1, 231 &prewire_cb, 232 &ctx)); 233 FAILIF_C (1 != ctx.total, 234 ctx_free (&ctx)); 235 ctx_free (&ctx); 236 237 /* ...and so is the starting row */ 238 memset (&ctx, 239 0, 240 sizeof (ctx)); 241 FAILIF (0 >= 242 TALER_EXCHANGEDB_iterate_prewires (pg, 243 row1 + 1, 244 10, 245 &prewire_cb, 246 &ctx)); 247 FAILIF_C (1 != ctx.total, 248 ctx_free (&ctx)); 249 FAILIF_C (0 != strcmp (ctx.first_method, 250 "iban"), 251 ctx_free (&ctx)); 252 ctx_free (&ctx); 253 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 check_finished_and_failed (struct TALER_EXCHANGEDB_PostgresContext *pg) 265 { 266 struct PrewireContext ctx; 267 uint64_t row1; 268 uint64_t row2; 269 270 memset (&ctx, 271 0, 272 sizeof (ctx)); 273 FAILIF (0 >= 274 TALER_EXCHANGEDB_iterate_prewires (pg, 275 0, 276 10, 277 &prewire_cb, 278 &ctx)); 279 FAILIF_C (2 != ctx.total, 280 ctx_free (&ctx)); 281 row1 = ctx.first_row; 282 ctx_free (&ctx); 283 284 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 285 TALER_EXCHANGEDB_update_to_prewire_finished (pg, 286 row1)); 287 memset (&ctx, 288 0, 289 sizeof (ctx)); 290 FAILIF (0 >= 291 TALER_EXCHANGEDB_iterate_prewires (pg, 292 0, 293 10, 294 &prewire_cb, 295 &ctx)); 296 FAILIF_C (1 != ctx.total, 297 ctx_free (&ctx)); 298 row2 = ctx.first_row; 299 ctx_free (&ctx); 300 FAILIF (row2 == row1); 301 302 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 303 TALER_EXCHANGEDB_update_to_prewire_failed (pg, 304 row2)); 305 memset (&ctx, 306 0, 307 sizeof (ctx)); 308 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 309 TALER_EXCHANGEDB_iterate_prewires (pg, 310 0, 311 10, 312 &prewire_cb, 313 &ctx)); 314 FAILIF_C (0 != ctx.total, 315 ctx_free (&ctx)); 316 ctx_free (&ctx); 317 318 /* both rows are still on file, one finished and one failed */ 319 FAILIF (2 != TDB_count (pg, 320 "FROM prewire")); 321 FAILIF (1 != TDB_count (pg, 322 "FROM prewire WHERE finished")); 323 FAILIF (1 != TDB_count (pg, 324 "FROM prewire WHERE failed")); 325 /* marking again is still reported as a row touched */ 326 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 327 TALER_EXCHANGEDB_update_to_prewire_finished (pg, 328 row1)); 329 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 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 for (unsigned int i = 0; i < 4; i++) 349 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 memset (&rc, 356 0, 357 sizeof (rc)); 358 FAILIF (0 >= 359 TALER_EXCHANGEDB_iterate_prewires (pg, 360 0, 361 10, 362 &rows_cb, 363 &rc)); 364 FAILIF (4 != rc.total); 365 memcpy (r, 366 rc.rows, 367 sizeof (r)); 368 369 /* without shards, nothing is below them */ 370 memset (&rc, 371 0, 372 sizeof (rc)); 373 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 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 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 memset (&rc, 399 0, 400 sizeof (rc)); 401 FAILIF (0 >= 402 TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg, 403 "transfer", 404 10, 405 &rows_cb, 406 &rc)); 407 FAILIF (2 != rc.total); 408 FAILIF (r[0] != rc.rows[0]); 409 FAILIF (r[1] != rc.rows[1]); 410 411 /* the limit is honoured */ 412 memset (&rc, 413 0, 414 sizeof (rc)); 415 FAILIF (0 >= 416 TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg, 417 "transfer", 418 1, 419 &rows_cb, 420 &rc)); 421 FAILIF (1 != rc.total); 422 FAILIF (r[0] != rc.rows[0]); 423 424 /* finished rows drop out */ 425 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 426 TALER_EXCHANGEDB_update_to_prewire_finished (pg, 427 r[0])); 428 memset (&rc, 429 0, 430 sizeof (rc)); 431 FAILIF (0 >= 432 TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg, 433 "transfer", 434 10, 435 &rows_cb, 436 &rc)); 437 FAILIF (1 != rc.total); 438 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 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 memset (&rc, 449 0, 450 sizeof (rc)); 451 FAILIF (0 >= 452 TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg, 453 "transfer", 454 10, 455 &rows_cb, 456 &rc)); 457 FAILIF (3 != rc.total); 458 FAILIF (r[1] != rc.rows[0]); 459 FAILIF (r[2] != rc.rows[1]); 460 FAILIF (r[3] != rc.rows[2]); 461 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 main (int argc, 483 char *const *argv) 484 { 485 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 */