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_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 23 : ts (uint64_t secs)
64 : {
65 23 : struct GNUNET_TIME_Absolute abs = {
66 23 : .abs_value_us = secs * 1000LLU * 1000LLU
67 : };
68 :
69 23 : 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 3 : 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 3 : struct FeeContext *ctx = cls;
108 :
109 : (void) start_date;
110 : (void) end_date;
111 : (void) master_sig;
112 3 : ctx->total++;
113 3 : ctx->value_sum += fees->wire.value;
114 3 : }
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 3 : 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 3 : fees.wire = TDB_amount (wire);
140 3 : fees.closing = TDB_amount (closing);
141 3 : TDB_FILL (master_sig,
142 : seed);
143 3 : 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 1 : 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 1 : struct FeeContext ctx = { 0 };
166 : uint64_t rowid;
167 :
168 1 : 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 1 : 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 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
184 : TALER_EXCHANGEDB_iterate_wire_fees (pg,
185 : METHOD,
186 : &fee_cb,
187 : &ctx));
188 1 : FAILIF (0 != ctx.total);
189 1 : 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 1 : 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 1 : struct TALER_Amount expect_wire = TDB_amount ("1");
206 1 : struct TALER_Amount expect_closing = TDB_amount ("2");
207 : struct TALER_MasterSignatureP master_sig;
208 : struct TALER_MasterSignatureP expect_sig;
209 1 : uint64_t rowid = 0;
210 :
211 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
212 : add_fee (pg,
213 : T0,
214 : T0 + HOUR,
215 : "1",
216 : "2",
217 : 10));
218 1 : TDB_FILL (expect_sig,
219 : 10);
220 1 : 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 1 : FAILIF (0 == rowid);
230 1 : FAILIF (GNUNET_TIME_timestamp_cmp (start_date,
231 : !=,
232 : ts (T0)));
233 1 : FAILIF (GNUNET_TIME_timestamp_cmp (end_date,
234 : !=,
235 : ts (T0 + HOUR)));
236 1 : FAILIF (0 != TALER_amount_cmp (&fees.wire,
237 : &expect_wire));
238 1 : FAILIF (0 != TALER_amount_cmp (&fees.closing,
239 : &expect_closing));
240 1 : FAILIF (0 != GNUNET_memcmp (&master_sig,
241 : &expect_sig));
242 :
243 : /* the interval is half-open: the end is not covered... */
244 1 : 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 1 : 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 1 : 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 1 : 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 1 : check_by_time (struct TALER_EXCHANGEDB_PostgresContext *pg)
286 : {
287 : struct TALER_WireFeeSet fees;
288 1 : 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 1 : 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 1 : 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 1 : memset (&fees,
309 : 0,
310 : sizeof (fees));
311 1 : 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 1 : 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 1 : memset (&fees,
323 : 0,
324 : sizeof (fees));
325 1 : FAILIF (2 !=
326 : TALER_EXCHANGEDB_get_wire_fee_by_time (pg,
327 : METHOD,
328 : ts (T0),
329 : ts (T0 + 2 * HOUR),
330 : &fees));
331 1 : FAILIF (0 != TALER_amount_cmp (&fees.wire,
332 : &expect_wire));
333 :
334 : /* three periods that disagree: success, but an invalid amount */
335 1 : memset (&fees,
336 : 0,
337 : sizeof (fees));
338 1 : FAILIF (0 >
339 : TALER_EXCHANGEDB_get_wire_fee_by_time (pg,
340 : METHOD,
341 : ts (T0),
342 : ts (T0 + 3 * HOUR),
343 : &fees));
344 1 : FAILIF (GNUNET_OK ==
345 : TALER_amount_is_valid (&fees.wire));
346 1 : FAILIF (GNUNET_OK ==
347 : TALER_amount_is_valid (&fees.closing));
348 :
349 : /* a range outside every period finds nothing */
350 1 : 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 1 : memset (&ctx,
358 : 0,
359 : sizeof (ctx));
360 1 : FAILIF (0 >=
361 : TALER_EXCHANGEDB_iterate_wire_fees (pg,
362 : METHOD,
363 : &fee_cb,
364 : &ctx));
365 1 : FAILIF (3 != ctx.total);
366 1 : FAILIF (1 + 1 + 5 != ctx.value_sum);
367 :
368 1 : memset (&ctx,
369 : 0,
370 : sizeof (ctx));
371 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
372 : TALER_EXCHANGEDB_iterate_wire_fees (pg,
373 : "iban",
374 : &fee_cb,
375 : &ctx));
376 1 : FAILIF (0 != ctx.total);
377 1 : 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 1 : main (int argc,
397 : char *const *argv)
398 : {
399 1 : 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 */
|