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