test_wire_fee.c (13202B)
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_fee.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `wire_fee` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_insert_wire_fee(), 23 * #TALER_EXCHANGEDB_get_wire_fee(), 24 * #TALER_EXCHANGEDB_get_wire_fee_by_time() and 25 * #TALER_EXCHANGEDB_iterate_wire_fees(). 26 * 27 * `wire_fee` has no foreign keys. What the checks pin down is the shape of 28 * the validity intervals: `get_wire_fee` treats them as [start,end), and 29 * `get_wire_fee_by_time` returns an invalid amount rather than an error 30 * when the range it is asked about spans two different fees. 31 */ 32 #include "test_common.h" 33 #include "exchange-database/insert_wire_fee.h" 34 #include "exchange-database/get_wire_fee.h" 35 #include "exchange-database/get_wire_fee_by_time.h" 36 #include "exchange-database/iterate_wire_fees.h" 37 38 39 /** 40 * Wire method the checks use. 41 */ 42 #define METHOD "x-taler-bank" 43 44 /** 45 * Start of the first fee period; a fixed point in time so that the checks 46 * do not depend on when they run. 47 */ 48 #define T0 1600000000 49 50 /** 51 * One hour, in seconds. 52 */ 53 #define HOUR 3600 54 55 56 /** 57 * Build a timestamp from a number of seconds since the epoch. 58 * 59 * @param secs seconds since the epoch 60 * @return the timestamp 61 */ 62 static struct GNUNET_TIME_Timestamp 63 ts (uint64_t secs) 64 { 65 struct GNUNET_TIME_Absolute abs = { 66 .abs_value_us = secs * 1000LLU * 1000LLU 67 }; 68 69 return GNUNET_TIME_absolute_to_timestamp (abs); 70 } 71 72 73 /** 74 * Closure for #fee_cb(). 75 */ 76 struct FeeContext 77 { 78 /** 79 * How many fee rows did the callback see? 80 */ 81 unsigned int total; 82 83 /** 84 * Sum of the whole-unit parts of the wire fees seen, as a cheap way to 85 * tell which rows were reported. 86 */ 87 uint64_t value_sum; 88 }; 89 90 91 /** 92 * Callback for #TALER_EXCHANGEDB_iterate_wire_fees(). 93 * 94 * @param cls a `struct FeeContext *` 95 * @param fees the fees of this period 96 * @param start_date start of the period 97 * @param end_date end of the period 98 * @param master_sig signature over the period 99 */ 100 static void 101 fee_cb (void *cls, 102 const struct TALER_WireFeeSet *fees, 103 struct GNUNET_TIME_Timestamp start_date, 104 struct GNUNET_TIME_Timestamp end_date, 105 const struct TALER_MasterSignatureP *master_sig) 106 { 107 struct FeeContext *ctx = cls; 108 109 (void) start_date; 110 (void) end_date; 111 (void) master_sig; 112 ctx->total++; 113 ctx->value_sum += fees->wire.value; 114 } 115 116 117 /** 118 * Insert a fee period. 119 * 120 * @param pg the database context 121 * @param from start of the period 122 * @param until end of the period 123 * @param wire wire fee, e.g. "1" 124 * @param closing closing fee, e.g. "2" 125 * @param seed seed for the master signature 126 * @return transaction status 127 */ 128 static enum GNUNET_DB_QueryStatus 129 add_fee (struct TALER_EXCHANGEDB_PostgresContext *pg, 130 uint64_t from, 131 uint64_t until, 132 const char *wire, 133 const char *closing, 134 uint32_t seed) 135 { 136 struct TALER_WireFeeSet fees; 137 struct TALER_MasterSignatureP master_sig; 138 139 fees.wire = TDB_amount (wire); 140 fees.closing = TDB_amount (closing); 141 TDB_FILL (master_sig, 142 seed); 143 return TALER_EXCHANGEDB_insert_wire_fee (pg, 144 METHOD, 145 ts (from), 146 ts (until), 147 &fees, 148 &master_sig); 149 } 150 151 152 /** 153 * No fees means no lookups succeed and the iterator sees nothing. 154 * 155 * @param pg the database context 156 * @return 0 on success 157 */ 158 static int 159 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 160 { 161 struct GNUNET_TIME_Timestamp start_date; 162 struct GNUNET_TIME_Timestamp end_date; 163 struct TALER_WireFeeSet fees; 164 struct TALER_MasterSignatureP master_sig; 165 struct FeeContext ctx = { 0 }; 166 uint64_t rowid; 167 168 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 169 TALER_EXCHANGEDB_get_wire_fee (pg, 170 METHOD, 171 ts (T0), 172 &rowid, 173 &start_date, 174 &end_date, 175 &fees, 176 &master_sig)); 177 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 178 TALER_EXCHANGEDB_get_wire_fee_by_time (pg, 179 METHOD, 180 ts (T0), 181 ts (T0 + HOUR), 182 &fees)); 183 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 184 TALER_EXCHANGEDB_iterate_wire_fees (pg, 185 METHOD, 186 &fee_cb, 187 &ctx)); 188 FAILIF (0 != ctx.total); 189 return 0; 190 } 191 192 193 /** 194 * A single fee period is found inside its interval and nowhere else. 195 * 196 * @param pg the database context 197 * @return 0 on success 198 */ 199 static int 200 check_single_period (struct TALER_EXCHANGEDB_PostgresContext *pg) 201 { 202 struct GNUNET_TIME_Timestamp start_date; 203 struct GNUNET_TIME_Timestamp end_date; 204 struct TALER_WireFeeSet fees; 205 struct TALER_Amount expect_wire = TDB_amount ("1"); 206 struct TALER_Amount expect_closing = TDB_amount ("2"); 207 struct TALER_MasterSignatureP master_sig; 208 struct TALER_MasterSignatureP expect_sig; 209 uint64_t rowid = 0; 210 211 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 212 add_fee (pg, 213 T0, 214 T0 + HOUR, 215 "1", 216 "2", 217 10)); 218 TDB_FILL (expect_sig, 219 10); 220 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 221 TALER_EXCHANGEDB_get_wire_fee (pg, 222 METHOD, 223 ts (T0), 224 &rowid, 225 &start_date, 226 &end_date, 227 &fees, 228 &master_sig)); 229 FAILIF (0 == rowid); 230 FAILIF (GNUNET_TIME_timestamp_cmp (start_date, 231 !=, 232 ts (T0))); 233 FAILIF (GNUNET_TIME_timestamp_cmp (end_date, 234 !=, 235 ts (T0 + HOUR))); 236 FAILIF (0 != TALER_amount_cmp (&fees.wire, 237 &expect_wire)); 238 FAILIF (0 != TALER_amount_cmp (&fees.closing, 239 &expect_closing)); 240 FAILIF (0 != GNUNET_memcmp (&master_sig, 241 &expect_sig)); 242 243 /* the interval is half-open: the end is not covered... */ 244 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 245 TALER_EXCHANGEDB_get_wire_fee (pg, 246 METHOD, 247 ts (T0 + HOUR), 248 &rowid, 249 &start_date, 250 &end_date, 251 &fees, 252 &master_sig)); 253 /* ...and neither is anything before the start */ 254 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 255 TALER_EXCHANGEDB_get_wire_fee (pg, 256 METHOD, 257 ts (T0 - 1), 258 &rowid, 259 &start_date, 260 &end_date, 261 &fees, 262 &master_sig)); 263 /* another wire method has no fees at all */ 264 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 265 TALER_EXCHANGEDB_get_wire_fee (pg, 266 "iban", 267 ts (T0), 268 &rowid, 269 &start_date, 270 &end_date, 271 &fees, 272 &master_sig)); 273 return 0; 274 } 275 276 277 /** 278 * Over a range with one fee, get_wire_fee_by_time() returns it; over a 279 * range spanning two different fees it returns an invalid amount. 280 * 281 * @param pg the database context 282 * @return 0 on success 283 */ 284 static int 285 check_by_time (struct TALER_EXCHANGEDB_PostgresContext *pg) 286 { 287 struct TALER_WireFeeSet fees; 288 struct TALER_Amount expect_wire = TDB_amount ("1"); 289 struct FeeContext ctx; 290 291 /* a second period with the *same* fees, and a third with different ones */ 292 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 293 add_fee (pg, 294 T0 + HOUR, 295 T0 + 2 * HOUR, 296 "1", 297 "2", 298 11)); 299 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 300 add_fee (pg, 301 T0 + 2 * HOUR, 302 T0 + 3 * HOUR, 303 "5", 304 "6", 305 12)); 306 307 /* one period: the fee itself */ 308 memset (&fees, 309 0, 310 sizeof (fees)); 311 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 312 TALER_EXCHANGEDB_get_wire_fee_by_time (pg, 313 METHOD, 314 ts (T0), 315 ts (T0 + HOUR), 316 &fees)); 317 FAILIF (0 != TALER_amount_cmp (&fees.wire, 318 &expect_wire)); 319 320 /* two periods that agree: still the fee. The status is the number of 321 rows the range covered, so it is 2 rather than ONE_RESULT. */ 322 memset (&fees, 323 0, 324 sizeof (fees)); 325 FAILIF (2 != 326 TALER_EXCHANGEDB_get_wire_fee_by_time (pg, 327 METHOD, 328 ts (T0), 329 ts (T0 + 2 * HOUR), 330 &fees)); 331 FAILIF (0 != TALER_amount_cmp (&fees.wire, 332 &expect_wire)); 333 334 /* three periods that disagree: success, but an invalid amount */ 335 memset (&fees, 336 0, 337 sizeof (fees)); 338 FAILIF (0 > 339 TALER_EXCHANGEDB_get_wire_fee_by_time (pg, 340 METHOD, 341 ts (T0), 342 ts (T0 + 3 * HOUR), 343 &fees)); 344 FAILIF (GNUNET_OK == 345 TALER_amount_is_valid (&fees.wire)); 346 FAILIF (GNUNET_OK == 347 TALER_amount_is_valid (&fees.closing)); 348 349 /* a range outside every period finds nothing */ 350 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 351 TALER_EXCHANGEDB_get_wire_fee_by_time (pg, 352 METHOD, 353 ts (T0 + 10 * HOUR), 354 ts (T0 + 11 * HOUR), 355 &fees)); 356 357 memset (&ctx, 358 0, 359 sizeof (ctx)); 360 FAILIF (0 >= 361 TALER_EXCHANGEDB_iterate_wire_fees (pg, 362 METHOD, 363 &fee_cb, 364 &ctx)); 365 FAILIF (3 != ctx.total); 366 FAILIF (1 + 1 + 5 != ctx.value_sum); 367 368 memset (&ctx, 369 0, 370 sizeof (ctx)); 371 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 372 TALER_EXCHANGEDB_iterate_wire_fees (pg, 373 "iban", 374 &fee_cb, 375 &ctx)); 376 FAILIF (0 != ctx.total); 377 return 0; 378 } 379 380 381 /** 382 * The checks to run, in order. 383 */ 384 static const struct TDB_Test tests[] = { 385 { "wire-fee-empty", 386 &check_empty }, 387 { "wire-fee-single-period", 388 &check_single_period }, 389 { "wire-fee-by-time", 390 &check_by_time }, 391 { NULL, NULL } 392 }; 393 394 395 int 396 main (int argc, 397 char *const *argv) 398 { 399 return TDB_main (argc, 400 argv, 401 "test-wire-fee", 402 "Tests for the exchangedb `wire_fee' table", 403 tests); 404 } 405 406 407 /* end of test_wire_fee.c */