test_denominations.c (13802B)
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_denominations.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `denominations` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_insert_denomination_info(), 23 * #TALER_EXCHANGEDB_get_denomination_info(), 24 * #TALER_EXCHANGEDB_get_denomination_by_serial(), 25 * #TALER_EXCHANGEDB_get_denomination_pub_by_serial(), 26 * #TALER_EXCHANGEDB_get_denomination_meta(), 27 * #TALER_EXCHANGEDB_iterate_denomination_info() and 28 * #TALER_EXCHANGEDB_iterate_denominations(). 29 * 30 * `denominations` has no foreign keys, so the checks need no fixtures. The 31 * empty-table checks run first, before any denomination exists. 32 */ 33 #include "test_common.h" 34 #include "exchange-database/insert_denomination_info.h" 35 #include "exchange-database/get_denomination_info.h" 36 #include "exchange-database/get_denomination_by_serial.h" 37 #include "exchange-database/get_denomination_pub_by_serial.h" 38 #include "exchange-database/get_denomination_meta.h" 39 #include "exchange-database/iterate_denomination_info.h" 40 #include "exchange-database/iterate_denominations.h" 41 42 43 /** 44 * Check that nothing is found while the table is still empty. 45 * 46 * @param pg the database context 47 * @return 0 on success 48 */ 49 static int 50 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 51 { 52 struct TALER_DenominationHashP h_denom_pub; 53 struct TALER_EXCHANGEDB_DenominationKeyInformation issue; 54 struct TALER_EXCHANGEDB_DenominationKeyMetaData meta; 55 uint64_t serial; 56 57 TDB_FILL (h_denom_pub, 58 1); 59 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 60 TALER_EXCHANGEDB_get_denomination_info (pg, 61 &h_denom_pub, 62 &serial, 63 &issue)); 64 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 65 TALER_EXCHANGEDB_get_denomination_by_serial (pg, 66 42, 67 &issue)); 68 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 69 TALER_EXCHANGEDB_get_denomination_meta (pg, 70 &h_denom_pub, 71 &meta)); 72 return 0; 73 } 74 75 76 /** 77 * Closure for #count_info_cb() and #count_denoms_cb(). 78 */ 79 struct CountContext 80 { 81 /** 82 * Denomination we are looking for. 83 */ 84 const struct TDB_Denom *denom; 85 86 /** 87 * How many rows did the callback see in total? 88 */ 89 unsigned int total; 90 91 /** 92 * How many times did we see @e denom? 93 */ 94 unsigned int matched; 95 96 /** 97 * Set to true if @e denom was reported with data that does not match 98 * what we inserted. 99 */ 100 bool mismatch; 101 102 /** 103 * Recoup flag reported for @e denom. 104 */ 105 bool recoup_possible; 106 107 /** 108 * Row reported for @e denom. 109 */ 110 uint64_t serial; 111 }; 112 113 114 /** 115 * Callback for #TALER_EXCHANGEDB_iterate_denomination_info(). 116 * 117 * @param cls a `struct CountContext *` 118 * @param denom_serial table row of the denomination 119 * @param denom_pub public key of the denomination 120 * @param issue information about the denomination 121 */ 122 static void 123 count_info_cb (void *cls, 124 uint64_t denom_serial, 125 const struct TALER_DenominationPublicKey *denom_pub, 126 const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue) 127 { 128 struct CountContext *ctx = cls; 129 130 ctx->total++; 131 if (0 != GNUNET_memcmp (&issue->denom_hash, 132 &ctx->denom->h_denom_pub)) 133 return; 134 ctx->matched++; 135 ctx->serial = denom_serial; 136 if ( (0 != TALER_denom_pub_cmp (denom_pub, 137 &ctx->denom->pub)) || 138 (0 != TALER_amount_cmp (&issue->value, 139 &ctx->denom->issue.value)) || 140 (0 != TALER_amount_cmp (&issue->fees.deposit, 141 &ctx->denom->issue.fees.deposit)) ) 142 ctx->mismatch = true; 143 } 144 145 146 /** 147 * Callback for #TALER_EXCHANGEDB_iterate_denominations(). 148 * 149 * @param cls a `struct CountContext *` 150 * @param denom_pub public key of the denomination 151 * @param h_denom_pub hash of @a denom_pub 152 * @param meta meta data of the denomination 153 * @param master_sig master signature over the denomination 154 * @param recoup_possible true if the denomination was revoked 155 */ 156 static void 157 count_denoms_cb (void *cls, 158 const struct TALER_DenominationPublicKey *denom_pub, 159 const struct TALER_DenominationHashP *h_denom_pub, 160 const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta, 161 const struct TALER_MasterSignatureP *master_sig, 162 bool recoup_possible) 163 { 164 struct CountContext *ctx = cls; 165 166 ctx->total++; 167 if (0 != GNUNET_memcmp (h_denom_pub, 168 &ctx->denom->h_denom_pub)) 169 return; 170 ctx->matched++; 171 ctx->serial = meta->serial; 172 ctx->recoup_possible = recoup_possible; 173 if ( (0 != TALER_denom_pub_cmp (denom_pub, 174 &ctx->denom->pub)) || 175 (0 != GNUNET_memcmp (master_sig, 176 &ctx->denom->issue.signature)) || 177 (0 != TALER_amount_cmp (&meta->value, 178 &ctx->denom->issue.value)) || 179 (GNUNET_TIME_timestamp_cmp (meta->start, 180 !=, 181 ctx->denom->issue.start)) ) 182 ctx->mismatch = true; 183 } 184 185 186 /** 187 * Insert a denomination and read it back through every lookup the 188 * table offers. 189 * 190 * @param pg the database context 191 * @return 0 on success 192 */ 193 static int 194 check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg) 195 { 196 struct TDB_Denom denom; 197 struct TALER_EXCHANGEDB_DenominationKeyInformation issue; 198 struct TALER_EXCHANGEDB_DenominationKeyMetaData meta; 199 struct TALER_DenominationHashP other; 200 uint64_t serial; 201 202 /* TDB_denom() inserts and looks up the row it created. */ 203 TDB_denom (pg, 204 10, 205 "5", 206 "0.1", 207 &denom); 208 memset (&issue, 209 0, 210 sizeof (issue)); 211 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 212 TALER_EXCHANGEDB_get_denomination_info (pg, 213 &denom.h_denom_pub, 214 &serial, 215 &issue), 216 TDB_denom_free (&denom)); 217 FAILIF_C (0 != GNUNET_memcmp (&issue, 218 &denom.issue), 219 TDB_denom_free (&denom)); 220 FAILIF_C (serial != denom.serial, 221 TDB_denom_free (&denom)); 222 223 /* the denom_serial output is optional */ 224 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 225 TALER_EXCHANGEDB_get_denomination_info (pg, 226 &denom.h_denom_pub, 227 NULL, 228 &issue), 229 TDB_denom_free (&denom)); 230 231 memset (&issue, 232 0, 233 sizeof (issue)); 234 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 235 TALER_EXCHANGEDB_get_denomination_by_serial (pg, 236 serial, 237 &issue), 238 TDB_denom_free (&denom)); 239 FAILIF_C (0 != GNUNET_memcmp (&issue, 240 &denom.issue), 241 TDB_denom_free (&denom)); 242 /* the public key itself is available by serial, too */ 243 { 244 struct TALER_DenominationPublicKey pub = { 0 }; 245 246 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 247 TALER_EXCHANGEDB_get_denomination_pub_by_serial (pg, 248 serial, 249 &pub), 250 TDB_denom_free (&denom)); 251 FAILIF_C (0 != TALER_denom_pub_cmp (&pub, 252 &denom.pub), 253 TALER_denom_pub_free (&pub); TDB_denom_free (&denom)); 254 TALER_denom_pub_free (&pub); 255 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 256 TALER_EXCHANGEDB_get_denomination_pub_by_serial (pg, 257 serial + 1000, 258 &pub), 259 TDB_denom_free (&denom)); 260 } 261 262 memset (&meta, 263 0, 264 sizeof (meta)); 265 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 266 TALER_EXCHANGEDB_get_denomination_meta (pg, 267 &denom.h_denom_pub, 268 &meta), 269 TDB_denom_free (&denom)); 270 FAILIF_C (meta.serial != serial, 271 TDB_denom_free (&denom)); 272 FAILIF_C (0 != TALER_amount_cmp (&meta.value, 273 &denom.issue.value), 274 TDB_denom_free (&denom)); 275 FAILIF_C (0 != TALER_amount_cmp (&meta.fees.refund, 276 &denom.issue.fees.refund), 277 TDB_denom_free (&denom)); 278 FAILIF_C (GNUNET_TIME_timestamp_cmp (meta.expire_legal, 279 !=, 280 denom.issue.expire_legal), 281 TDB_denom_free (&denom)); 282 FAILIF_C (meta.age_mask.bits != denom.issue.age_mask.bits, 283 TDB_denom_free (&denom)); 284 285 /* ...and a denomination that was never inserted is still not found. */ 286 TDB_FILL (other, 287 99); 288 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 289 TALER_EXCHANGEDB_get_denomination_info (pg, 290 &other, 291 &serial, 292 &issue), 293 TDB_denom_free (&denom)); 294 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 295 TALER_EXCHANGEDB_get_denomination_meta (pg, 296 &other, 297 &meta), 298 TDB_denom_free (&denom)); 299 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 300 TALER_EXCHANGEDB_get_denomination_by_serial (pg, 301 denom.serial + 1000, 302 &issue), 303 TDB_denom_free (&denom)); 304 TDB_denom_free (&denom); 305 return 0; 306 } 307 308 309 /** 310 * Both iterators must report every denomination in the table. 311 * 312 * @param pg the database context 313 * @return 0 on success 314 */ 315 static int 316 check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) 317 { 318 struct TDB_Denom d1; 319 struct TDB_Denom d2; 320 struct CountContext ctx; 321 unsigned int seen; 322 323 TDB_denom (pg, 324 20, 325 "1", 326 "0.01", 327 &d1); 328 TDB_denom (pg, 329 21, 330 "2", 331 "0.02", 332 &d2); 333 memset (&ctx, 334 0, 335 sizeof (ctx)); 336 ctx.denom = &d2; 337 FAILIF_C (0 >= 338 TALER_EXCHANGEDB_iterate_denomination_info (pg, 339 &count_info_cb, 340 &ctx), 341 TDB_denom_free (&d1); TDB_denom_free (&d2)); 342 FAILIF_C (1 != ctx.matched, 343 TDB_denom_free (&d1); TDB_denom_free (&d2)); 344 FAILIF_C (ctx.mismatch, 345 TDB_denom_free (&d1); TDB_denom_free (&d2)); 346 FAILIF_C (ctx.serial != d2.serial, 347 TDB_denom_free (&d1); TDB_denom_free (&d2)); 348 seen = ctx.total; 349 FAILIF_C (seen < 2, 350 TDB_denom_free (&d1); TDB_denom_free (&d2)); 351 352 memset (&ctx, 353 0, 354 sizeof (ctx)); 355 ctx.denom = &d1; 356 FAILIF_C (0 >= 357 TALER_EXCHANGEDB_iterate_denominations (pg, 358 &count_denoms_cb, 359 &ctx), 360 TDB_denom_free (&d1); TDB_denom_free (&d2)); 361 FAILIF_C (1 != ctx.matched, 362 TDB_denom_free (&d1); TDB_denom_free (&d2)); 363 FAILIF_C (ctx.mismatch, 364 TDB_denom_free (&d1); TDB_denom_free (&d2)); 365 FAILIF_C (ctx.serial != d1.serial, 366 TDB_denom_free (&d1); TDB_denom_free (&d2)); 367 /* nothing was revoked in this binary */ 368 FAILIF_C (ctx.recoup_possible, 369 TDB_denom_free (&d1); TDB_denom_free (&d2)); 370 /* both iterators walk the same table, so they must agree on its size */ 371 FAILIF_C (ctx.total != seen, 372 TDB_denom_free (&d1); TDB_denom_free (&d2)); 373 TDB_denom_free (&d1); 374 TDB_denom_free (&d2); 375 return 0; 376 } 377 378 379 /** 380 * The checks to run, in order. 381 */ 382 static const struct TDB_Test tests[] = { 383 { "denominations-empty", 384 &check_empty }, 385 { "denominations-insert-and-lookup", 386 &check_insert_and_lookup }, 387 { "denominations-iterate", 388 &check_iterate }, 389 { NULL, NULL } 390 }; 391 392 393 int 394 main (int argc, 395 char *const *argv) 396 { 397 return TDB_main (argc, 398 argv, 399 "test-denominations", 400 "Tests for the exchangedb `denominations' table", 401 tests); 402 } 403 404 405 /* end of test_denominations.c */