test_common.h (14984B)
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_common.h 18 * @brief shared scaffolding for the per-table exchangedb tests 19 * @author Christian Grothoff 20 * 21 * Every `test_$TABLE.c` in this directory is a stand-alone binary that runs 22 * the exported database functions whose primary test table is `$TABLE` 23 * against a scratch database. This header holds the bits all of them need: 24 * the main loop, a raw-SQL escape hatch and fixtures for the rows that the 25 * functions under test reference by foreign key. 26 * 27 * The test data is deliberately bogus: keys are derived from a seed byte and 28 * signatures are never verifiable. The database layer does not check any of 29 * that, and building real signatures would only make the tests slower and 30 * harder to read. 31 */ 32 #ifndef TEST_COMMON_H 33 #define TEST_COMMON_H 34 35 #include "exchangedb_lib.h" 36 #include "helper.h" 37 #include "taler/taler_json_lib.h" 38 39 40 /** 41 * Currency the scratch database is configured for. Must match 42 * test_table.sh. 43 */ 44 #define CURRENCY "EUR" 45 46 47 /** 48 * Report a failed expectation and return 1 from the calling check. 49 */ 50 #define FAILIF(cond) \ 51 do { \ 52 if (! (cond)) break; \ 53 GNUNET_break (0); \ 54 fprintf (stderr, \ 55 "FAILED: %s at %s:%u\n", \ 56 # cond, __FILE__, __LINE__); \ 57 return 1; \ 58 } while (0) 59 60 61 /** 62 * Like #FAILIF(), but runs @a cleanup before returning. 63 */ 64 #define FAILIF_C(cond,cleanup) \ 65 do { \ 66 if (! (cond)) break; \ 67 GNUNET_break (0); \ 68 fprintf (stderr, \ 69 "FAILED: %s at %s:%u\n", \ 70 # cond, __FILE__, __LINE__); \ 71 cleanup; \ 72 return 1; \ 73 } while (0) 74 75 76 /** 77 * One check in a `test_$TABLE.c` binary. 78 */ 79 struct TDB_Test 80 { 81 /** 82 * Name of the check, used for the `-t` command-line option. NULL 83 * terminates the array. 84 */ 85 const char *name; 86 87 /** 88 * Function to run. Returns 0 on success. 89 */ 90 int (*fn)(struct TALER_EXCHANGEDB_PostgresContext *pg); 91 }; 92 93 94 /** 95 * Run the checks in @a tests. Connects to the database, creates the 96 * schema and then runs every check (or only the one named with `-t`). 97 * 98 * @param argc number of arguments in @a argv 99 * @param argv command line 100 * @param binary name of the binary, for the usage message 101 * @param description one-line description of the binary 102 * @param tests NULL-terminated array of checks to run 103 * @return 0 on success, 1 if a check failed, 77 if the database was 104 * unusable, 3 on a command-line error 105 */ 106 int 107 TDB_main (int argc, 108 char *const *argv, 109 const char *binary, 110 const char *description, 111 const struct TDB_Test *tests); 112 113 114 /** 115 * Run @a fmt on the database connection, outside of any transaction. 116 * Used to set up rows that no exported function can create, and to 117 * assert over the database from the outside. 118 * 119 * @param pg the database context 120 * @param fmt printf-style SQL statement(s) 121 * @return #GNUNET_OK on success 122 */ 123 enum GNUNET_GenericReturnValue 124 TDB_exec (struct TALER_EXCHANGEDB_PostgresContext *pg, 125 const char *fmt, 126 ...) 127 __attribute__ ((format (printf, 2, 3))); 128 129 130 /** 131 * Count the rows the SQL fragment @a fmt selects. The fragment is 132 * appended to "SELECT COUNT(*) AS n ", so it starts with FROM. 133 * 134 * @param pg the database context 135 * @param fmt printf-style SQL fragment, e.g. "FROM reserves WHERE ..." 136 * @return number of matching rows 137 */ 138 uint64_t 139 TDB_count (struct TALER_EXCHANGEDB_PostgresContext *pg, 140 const char *fmt, 141 ...) 142 __attribute__ ((format (printf, 2, 3))); 143 144 145 /** 146 * Convert @a str to an amount in our currency. 147 * 148 * @param str amount without the currency prefix, e.g. "10.5" 149 * @return the parsed amount 150 */ 151 struct TALER_Amount 152 TDB_amount (const char *str); 153 154 155 /** 156 * Fill @a size bytes at @a ptr with data derived from @a seed. Different 157 * seeds give different data, the same seed always gives the same data. 158 * 159 * @param[out] ptr buffer to fill 160 * @param size number of bytes to write to @a ptr 161 * @param seed seed to derive the data from 162 */ 163 void 164 TDB_fill (void *ptr, 165 size_t size, 166 uint32_t seed); 167 168 169 /** 170 * Fill the object @a x with data derived from @a seed. 171 */ 172 #define TDB_FILL(x,seed) TDB_fill (&(x), sizeof (x), seed) 173 174 175 /** 176 * Render @a size bytes at @a data as a lowercase hex string for use in an 177 * SQL literal. 178 * 179 * @param data binary data 180 * @param size number of bytes in @a data 181 * @return hex string, to be freed by the caller 182 */ 183 char * 184 TDB_hex (const void *data, 185 size_t size); 186 187 188 /** 189 * A denomination that exists in the `denominations` table. 190 */ 191 struct TDB_Denom 192 { 193 /** 194 * Private key; never used to sign anything meaningful. 195 */ 196 struct TALER_DenominationPrivateKey priv; 197 198 /** 199 * Public key, as stored in the database. 200 */ 201 struct TALER_DenominationPublicKey pub; 202 203 /** 204 * Hash of @e pub, the primary key of the denomination. 205 */ 206 struct TALER_DenominationHashP h_denom_pub; 207 208 /** 209 * Row of the denomination in the `denominations` table. 210 */ 211 uint64_t serial; 212 213 /** 214 * Value, fees and validity period as inserted. 215 */ 216 struct TALER_EXCHANGEDB_DenominationKeyInformation issue; 217 }; 218 219 220 /** 221 * Create a denomination and insert it into the `denominations` table. 222 * Fails hard if the insert does not work: a test that cannot set up its 223 * fixtures has nothing to say about the function under test. 224 * 225 * @param pg the database context 226 * @param seed seed for the key material and the master signature 227 * @param value value of a coin of this denomination, e.g. "5" 228 * @param fee fee charged for every operation on such a coin, e.g. "0.1" 229 * @param[out] denom set to the denomination 230 */ 231 void 232 TDB_denom (struct TALER_EXCHANGEDB_PostgresContext *pg, 233 uint32_t seed, 234 const char *value, 235 const char *fee, 236 struct TDB_Denom *denom); 237 238 239 /** 240 * Release the key material of @a denom. The row stays in the database. 241 * 242 * @param[in,out] denom denomination to clean up 243 */ 244 void 245 TDB_denom_free (struct TDB_Denom *denom); 246 247 248 /** 249 * Build an (invalid) denomination signature that the database layer will 250 * happily store and read back. 251 * 252 * @param seed seed to derive the signature from 253 * @param[out] sig set to the signature, to be freed with 254 * TALER_denom_sig_free() 255 */ 256 void 257 TDB_denom_sig (uint32_t seed, 258 struct TALER_DenominationSignature *sig); 259 260 261 /** 262 * Build an (invalid) blinded denomination signature that the database 263 * layer will happily store and read back. 264 * 265 * @param seed seed to derive the signature from 266 * @param[out] sig set to the signature, to be freed with 267 * TALER_blinded_denom_sig_free() 268 */ 269 void 270 TDB_blinded_denom_sig (uint32_t seed, 271 struct TALER_BlindedDenominationSignature *sig); 272 273 274 /** 275 * Create a coin of denomination @a denom and insert it into the 276 * `known_coins` table. Fails hard if the insert does not work. 277 * 278 * @param pg the database context 279 * @param denom denomination of the coin 280 * @param seed seed for the coin's key and signature 281 * @param[out] coin set to the coin, to be cleaned up with TDB_coin_free() 282 * @param[out] known_coin_id set to the row of the coin, may be NULL 283 */ 284 void 285 TDB_coin (struct TALER_EXCHANGEDB_PostgresContext *pg, 286 const struct TDB_Denom *denom, 287 uint32_t seed, 288 struct TALER_CoinPublicInfo *coin, 289 uint64_t *known_coin_id); 290 291 292 /** 293 * Release the signature of @a coin. The row stays in the database. 294 * 295 * @param[in,out] coin coin to clean up 296 */ 297 void 298 TDB_coin_free (struct TALER_CoinPublicInfo *coin); 299 300 301 /** 302 * A bank account known to the exchange. 303 */ 304 struct TDB_Account 305 { 306 /** 307 * Full payto URI of the account. 308 */ 309 struct TALER_FullPayto payto; 310 311 /** 312 * Hash of @e payto, the primary key in `wire_targets`. 313 */ 314 struct TALER_FullPaytoHashP h_full; 315 316 /** 317 * Hash of the normalized form of @e payto, the primary key in 318 * `kyc_targets`. 319 */ 320 struct TALER_NormalizedPaytoHashP h_normalized; 321 }; 322 323 324 /** 325 * Create a `kyc_targets` and a `wire_targets` row for a fresh bank 326 * account. Both tables are referenced by foreign keys all over the 327 * schema, but no exported function creates a row in them on its own. 328 * 329 * @param pg the database context 330 * @param seed seed for the account name 331 * @param[out] account set to the account, to be cleaned up with 332 * TDB_account_free() 333 */ 334 void 335 TDB_account (struct TALER_EXCHANGEDB_PostgresContext *pg, 336 uint32_t seed, 337 struct TDB_Account *account); 338 339 340 /** 341 * Release the payto URI of @a account. The rows stay in the database. 342 * 343 * @param[in,out] account account to clean up 344 */ 345 void 346 TDB_account_free (struct TDB_Account *account); 347 348 349 /** 350 * Create a reserve with the given @a balance, as 351 * exchange_do_purse_merge() does: without a `reserves_in` row, so the 352 * reserve has no origin account. 353 * 354 * @param pg the database context 355 * @param seed seed for the reserve's public key 356 * @param balance balance to give the reserve, e.g. "10" 357 * @param[out] reserve_pub set to the public key of the reserve 358 */ 359 void 360 TDB_reserve (struct TALER_EXCHANGEDB_PostgresContext *pg, 361 uint32_t seed, 362 const char *balance, 363 struct TALER_ReservePublicKeyP *reserve_pub); 364 365 366 /** 367 * A purse, as TDB_purse() created it. 368 */ 369 struct TDB_Purse 370 { 371 /** 372 * Public key of the purse. 373 */ 374 struct TALER_PurseContractPublicKeyP purse_pub; 375 376 /** 377 * Key that allows merging the purse. 378 */ 379 struct TALER_PurseMergePublicKeyP merge_pub; 380 381 /** 382 * Contract the purse is for. 383 */ 384 struct TALER_PrivateContractHashP h_contract_terms; 385 386 /** 387 * Signature over the purse's meta data. 388 */ 389 struct TALER_PurseContractSignatureP purse_sig; 390 391 /** 392 * When the purse expires. 393 */ 394 struct GNUNET_TIME_Timestamp purse_expiration; 395 396 /** 397 * Target amount of the purse. 398 */ 399 struct TALER_Amount amount; 400 }; 401 402 403 /** 404 * Create a purse with #TALER_EXCHANGEDB_insert_purse_request(). Fails 405 * hard if the purse cannot be created. 406 * 407 * @param pg the database context 408 * @param seed seed for the purse's keys, contract and signature 409 * @param amount target amount of the purse, e.g. "5" 410 * @param purse_expiration when the purse expires 411 * @param[out] purse set to the purse 412 */ 413 void 414 TDB_purse (struct TALER_EXCHANGEDB_PostgresContext *pg, 415 uint32_t seed, 416 const char *amount, 417 struct GNUNET_TIME_Timestamp purse_expiration, 418 struct TDB_Purse *purse); 419 420 421 /** 422 * A batch deposit of one coin, as TDB_deposit() made it. 423 */ 424 struct TDB_Deposit 425 { 426 /** 427 * Merchant the coin was deposited with. 428 */ 429 struct TALER_MerchantPublicKeyP merchant_pub; 430 431 /** 432 * Merchant signature over the contract. 433 */ 434 struct TALER_MerchantSignatureP merchant_sig; 435 436 /** 437 * Contract the deposit is for. 438 */ 439 struct TALER_PrivateContractHashP h_contract_terms; 440 441 /** 442 * Salt the merchant used for @e h_wire. 443 */ 444 struct TALER_WireSaltP wire_salt; 445 446 /** 447 * Salted hash of the merchant's account. 448 */ 449 struct TALER_MerchantWireHashP h_wire; 450 451 /** 452 * When the merchant wants to be paid. 453 */ 454 struct GNUNET_TIME_Timestamp wire_deadline; 455 456 /** 457 * Until when the merchant may refund. 458 */ 459 struct GNUNET_TIME_Timestamp refund_deadline; 460 461 /** 462 * Time the exchange recorded for the deposit. 463 */ 464 struct GNUNET_TIME_Timestamp exchange_timestamp; 465 466 /** 467 * Row of the deposit in `batch_deposits`. 468 */ 469 uint64_t serial; 470 }; 471 472 473 /** 474 * Deposit @a coin with a fresh merchant, creating the `batch_deposits` 475 * and `coin_deposits` rows for it. Fails hard if the deposit does not go 476 * through. 477 * 478 * @param pg the database context 479 * @param account account the merchant wants to be paid at 480 * @param coin coin to deposit 481 * @param seed seed for the merchant key, the contract and the salt 482 * @param amount amount to deposit, e.g. "1" 483 * @param fee deposit fee of the coin's denomination, e.g. "0.1" 484 * @param wire_deadline when the merchant wants to be paid 485 * @param refund_deadline until when the merchant may refund 486 * @param[out] dep set to the deposit 487 */ 488 void 489 TDB_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg, 490 const struct TDB_Account *account, 491 const struct TALER_CoinPublicInfo *coin, 492 uint32_t seed, 493 const char *amount, 494 const char *fee, 495 struct GNUNET_TIME_Timestamp wire_deadline, 496 struct GNUNET_TIME_Timestamp refund_deadline, 497 struct TDB_Deposit *dep); 498 499 500 /** 501 * Withdraw one coin of @a denom from @a reserve_pub, creating a `withdraw` 502 * row for it. The reserve must hold at least @a amount. 503 * 504 * @param pg the database context 505 * @param denom denomination of the coin to withdraw 506 * @param reserve_pub reserve to withdraw from 507 * @param seed seed for the planchet hash and the signatures 508 * @param amount amount to withdraw, e.g. "5" 509 * @return row of the new entry in `withdraw` 510 */ 511 uint64_t 512 TDB_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg, 513 const struct TDB_Denom *denom, 514 const struct TALER_ReservePublicKeyP *reserve_pub, 515 uint32_t seed, 516 const char *amount); 517 518 519 /** 520 * Wire @a amount from @a account into a fresh reserve, creating a 521 * `reserves_in` row for it. 522 * 523 * @param pg the database context 524 * @param account account the money comes from 525 * @param seed seed for the reserve's public key and the wire subject 526 * @param amount amount that was wired, e.g. "10" 527 * @param[out] reserve_pub set to the public key of the reserve 528 * @return row of the new entry in `reserves_in` 529 */ 530 uint64_t 531 TDB_reserve_in (struct TALER_EXCHANGEDB_PostgresContext *pg, 532 const struct TDB_Account *account, 533 uint32_t seed, 534 const char *amount, 535 struct TALER_ReservePublicKeyP *reserve_pub); 536 537 538 #endif