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_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 1 : 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 1 : TDB_FILL (h_denom_pub,
58 : 1);
59 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
60 : TALER_EXCHANGEDB_get_denomination_info (pg,
61 : &h_denom_pub,
62 : &serial,
63 : &issue));
64 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
65 : TALER_EXCHANGEDB_get_denomination_by_serial (pg,
66 : 42,
67 : &issue));
68 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
69 : TALER_EXCHANGEDB_get_denomination_meta (pg,
70 : &h_denom_pub,
71 : &meta));
72 1 : 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 3 : 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 3 : struct CountContext *ctx = cls;
129 :
130 3 : ctx->total++;
131 3 : if (0 != GNUNET_memcmp (&issue->denom_hash,
132 : &ctx->denom->h_denom_pub))
133 2 : return;
134 1 : ctx->matched++;
135 1 : ctx->serial = denom_serial;
136 1 : if ( (0 != TALER_denom_pub_cmp (denom_pub,
137 2 : &ctx->denom->pub)) ||
138 1 : (0 != TALER_amount_cmp (&issue->value,
139 2 : &ctx->denom->issue.value)) ||
140 1 : (0 != TALER_amount_cmp (&issue->fees.deposit,
141 1 : &ctx->denom->issue.fees.deposit)) )
142 0 : 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 3 : 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 3 : struct CountContext *ctx = cls;
165 :
166 3 : ctx->total++;
167 3 : if (0 != GNUNET_memcmp (h_denom_pub,
168 : &ctx->denom->h_denom_pub))
169 2 : return;
170 1 : ctx->matched++;
171 1 : ctx->serial = meta->serial;
172 1 : ctx->recoup_possible = recoup_possible;
173 1 : if ( (0 != TALER_denom_pub_cmp (denom_pub,
174 1 : &ctx->denom->pub)) ||
175 1 : (0 != GNUNET_memcmp (master_sig,
176 1 : &ctx->denom->issue.signature)) ||
177 1 : (0 != TALER_amount_cmp (&meta->value,
178 1 : &ctx->denom->issue.value)) ||
179 1 : (GNUNET_TIME_timestamp_cmp (meta->start,
180 : !=,
181 : ctx->denom->issue.start)) )
182 0 : 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 1 : 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 1 : TDB_denom (pg,
204 : 10,
205 : "5",
206 : "0.1",
207 : &denom);
208 1 : memset (&issue,
209 : 0,
210 : sizeof (issue));
211 1 : 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 1 : FAILIF_C (0 != GNUNET_memcmp (&issue,
218 : &denom.issue),
219 : TDB_denom_free (&denom));
220 1 : FAILIF_C (serial != denom.serial,
221 : TDB_denom_free (&denom));
222 :
223 : /* the denom_serial output is optional */
224 1 : 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 1 : memset (&issue,
232 : 0,
233 : sizeof (issue));
234 1 : 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 1 : 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 1 : struct TALER_DenominationPublicKey pub = { 0 };
245 :
246 1 : 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 1 : FAILIF_C (0 != TALER_denom_pub_cmp (&pub,
252 : &denom.pub),
253 : TALER_denom_pub_free (&pub); TDB_denom_free (&denom));
254 1 : TALER_denom_pub_free (&pub);
255 1 : 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 1 : memset (&meta,
263 : 0,
264 : sizeof (meta));
265 1 : 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 1 : FAILIF_C (meta.serial != serial,
271 : TDB_denom_free (&denom));
272 1 : FAILIF_C (0 != TALER_amount_cmp (&meta.value,
273 : &denom.issue.value),
274 : TDB_denom_free (&denom));
275 1 : FAILIF_C (0 != TALER_amount_cmp (&meta.fees.refund,
276 : &denom.issue.fees.refund),
277 : TDB_denom_free (&denom));
278 1 : FAILIF_C (GNUNET_TIME_timestamp_cmp (meta.expire_legal,
279 : !=,
280 : denom.issue.expire_legal),
281 : TDB_denom_free (&denom));
282 1 : 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 1 : TDB_FILL (other,
287 : 99);
288 1 : 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 1 : 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 1 : 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 1 : TDB_denom_free (&denom);
305 1 : 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 1 : 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 1 : TDB_denom (pg,
324 : 20,
325 : "1",
326 : "0.01",
327 : &d1);
328 1 : TDB_denom (pg,
329 : 21,
330 : "2",
331 : "0.02",
332 : &d2);
333 1 : memset (&ctx,
334 : 0,
335 : sizeof (ctx));
336 1 : ctx.denom = &d2;
337 1 : 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 1 : FAILIF_C (1 != ctx.matched,
343 : TDB_denom_free (&d1); TDB_denom_free (&d2));
344 1 : FAILIF_C (ctx.mismatch,
345 : TDB_denom_free (&d1); TDB_denom_free (&d2));
346 1 : FAILIF_C (ctx.serial != d2.serial,
347 : TDB_denom_free (&d1); TDB_denom_free (&d2));
348 1 : seen = ctx.total;
349 1 : FAILIF_C (seen < 2,
350 : TDB_denom_free (&d1); TDB_denom_free (&d2));
351 :
352 1 : memset (&ctx,
353 : 0,
354 : sizeof (ctx));
355 1 : ctx.denom = &d1;
356 1 : 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 1 : FAILIF_C (1 != ctx.matched,
362 : TDB_denom_free (&d1); TDB_denom_free (&d2));
363 1 : FAILIF_C (ctx.mismatch,
364 : TDB_denom_free (&d1); TDB_denom_free (&d2));
365 1 : FAILIF_C (ctx.serial != d1.serial,
366 : TDB_denom_free (&d1); TDB_denom_free (&d2));
367 : /* nothing was revoked in this binary */
368 1 : 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 1 : FAILIF_C (ctx.total != seen,
372 : TDB_denom_free (&d1); TDB_denom_free (&d2));
373 1 : TDB_denom_free (&d1);
374 1 : TDB_denom_free (&d2);
375 1 : 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 1 : main (int argc,
395 : char *const *argv)
396 : {
397 1 : 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 */
|