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 auditordb/test_auditordb_regression.c
18 : * @brief regression tests for individual auditor DB operations
19 : * @author Christian Grothoff
20 : *
21 : * Each test function in this file pins down the behaviour of one
22 : * auditor DB operation that was found to be wrong. Keep them small
23 : * and independent: the file is meant to grow one function per fixed
24 : * defect.
25 : */
26 : #include <gnunet/gnunet_db_lib.h>
27 : #include "auditordb_lib.h"
28 : #include "auditor-database/create_tables.h"
29 : #include "auditor-database/drop_tables.h"
30 : #include "auditor-database/get_auditor_progress.h"
31 : #include "auditor-database/get_balance.h"
32 : #include "auditor-database/insert_auditor_progress.h"
33 : #include "auditor-database/insert_balance.h"
34 : #include "auditor-database/insert_purse_not_closed_inconsistencies.h"
35 : #include "auditor-database/iterate_purse_not_closed_inconsistencies.h"
36 : #include "auditor-database/preflight.h"
37 : #include "auditor-database/start.h"
38 :
39 : /**
40 : * Currency we use, must match CURRENCY in "test-auditor-db-postgres.conf".
41 : */
42 : #define CURRENCY "EUR"
43 :
44 : /**
45 : * Complain and fail the test if @a cond is true.
46 : */
47 : #define FAILIF(cond) \
48 : do { \
49 : if (! (cond)) break; \
50 : GNUNET_break (0); \
51 : return GNUNET_SYSERR; \
52 : } while (0)
53 :
54 :
55 : /**
56 : * Global result from the testcase.
57 : */
58 : static int result = -1;
59 :
60 : /**
61 : * Database connection under test.
62 : */
63 : static struct TALER_AUDITORDB_PostgresContext *pg;
64 :
65 :
66 : /**
67 : * Parse @a str into @a amount, asserting success.
68 : *
69 : * @param str amount to parse, without currency
70 : * @param[out] amount where to write the result
71 : */
72 : static void
73 4 : amount (const char *str,
74 : struct TALER_Amount *amount)
75 : {
76 : char buf[128];
77 :
78 4 : GNUNET_snprintf (buf,
79 : sizeof (buf),
80 : "%s:%s",
81 : CURRENCY,
82 : str);
83 4 : GNUNET_assert (GNUNET_OK ==
84 : TALER_string_to_amount (buf,
85 : amount));
86 4 : }
87 :
88 :
89 : /**
90 : * A-1: neither TALER_AUDITORDB_get_balance() nor
91 : * TALER_AUDITORDB_get_auditor_progress() can report "key not on file"
92 : * (auditor_do_get_balance() emits one row per key either way), so an
93 : * auditor helper cannot know whether to insert or to update. Both
94 : * operations must therefore store the value they are given no matter
95 : * which one the caller picks.
96 : *
97 : * @return #GNUNET_OK on success
98 : */
99 : static enum GNUNET_GenericReturnValue
100 1 : test_checkpoint_upsert (void)
101 : {
102 : struct TALER_Amount one;
103 : struct TALER_Amount two;
104 : struct TALER_Amount got;
105 : uint64_t off;
106 :
107 1 : amount ("1",
108 : &one);
109 1 : amount ("2",
110 : &two);
111 :
112 : /* insert_balance() on a key that does not exist yet must create it. */
113 1 : FAILIF (0 >
114 : TALER_AUDITORDB_insert_balance (pg,
115 : "a1-update-only",
116 : &one,
117 : NULL));
118 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
119 : TALER_AUDITORDB_get_balance (pg,
120 : "a1-update-only",
121 : &got,
122 : NULL));
123 1 : FAILIF (0 !=
124 : TALER_amount_cmp (&got,
125 : &one));
126 :
127 : /* insert_balance() on a key that already exists must overwrite it. */
128 1 : FAILIF (0 >
129 : TALER_AUDITORDB_insert_balance (pg,
130 : "a1-insert-twice",
131 : &one,
132 : NULL));
133 1 : FAILIF (0 >
134 : TALER_AUDITORDB_insert_balance (pg,
135 : "a1-insert-twice",
136 : &two,
137 : NULL));
138 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
139 : TALER_AUDITORDB_get_balance (pg,
140 : "a1-insert-twice",
141 : &got,
142 : NULL));
143 1 : FAILIF (0 !=
144 : TALER_amount_cmp (&got,
145 : &two));
146 :
147 : /* Same two properties for the progress points. */
148 1 : FAILIF (0 >
149 : TALER_AUDITORDB_insert_auditor_progress (pg,
150 : "a1-update-only",
151 : 42,
152 : NULL));
153 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
154 : TALER_AUDITORDB_get_auditor_progress (pg,
155 : "a1-update-only",
156 : &off,
157 : NULL));
158 1 : FAILIF (42 != off);
159 :
160 1 : FAILIF (0 >
161 : TALER_AUDITORDB_insert_auditor_progress (pg,
162 : "a1-insert-twice",
163 : 42,
164 : NULL));
165 1 : FAILIF (0 >
166 : TALER_AUDITORDB_insert_auditor_progress (pg,
167 : "a1-insert-twice",
168 : 43,
169 : NULL));
170 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
171 : TALER_AUDITORDB_get_auditor_progress (pg,
172 : "a1-insert-twice",
173 : &off,
174 : NULL));
175 1 : FAILIF (43 != off);
176 1 : return GNUNET_OK;
177 : }
178 :
179 :
180 : /**
181 : * Callback for #test_purse_not_closed_dedup(), keeps the last row seen.
182 : *
183 : * @param cls a `struct TALER_AUDITORDB_PurseNotClosedInconsistencies *`
184 : * @param dc the row
185 : * @return #GNUNET_OK
186 : */
187 : static enum GNUNET_GenericReturnValue
188 1 : purse_not_closed_cb (
189 : void *cls,
190 : const struct TALER_AUDITORDB_PurseNotClosedInconsistencies *dc)
191 : {
192 1 : struct TALER_AUDITORDB_PurseNotClosedInconsistencies *last = cls;
193 :
194 1 : *last = *dc;
195 1 : return GNUNET_OK;
196 : }
197 :
198 :
199 : /**
200 : * A-5: the purse auditor re-reports every expired purse on every round.
201 : * Without dedup on purse_pub the report table grows without bound.
202 : *
203 : * @return #GNUNET_OK on success
204 : */
205 : static enum GNUNET_GenericReturnValue
206 1 : test_purse_not_closed_dedup (void)
207 : {
208 1 : struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnc = {
209 1 : .expiration_date = GNUNET_TIME_absolute_get ()
210 : };
211 1 : struct TALER_AUDITORDB_PurseNotClosedInconsistencies last = { 0 };
212 : struct TALER_Amount three;
213 :
214 1 : GNUNET_CRYPTO_random_block (&pnc.purse_pub,
215 : sizeof (pnc.purse_pub));
216 1 : amount ("1",
217 : &pnc.amount);
218 1 : amount ("3",
219 : &three);
220 4 : for (unsigned int round = 0; round < 3; round++)
221 : {
222 3 : if (2 == round)
223 1 : pnc.amount = three;
224 3 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
225 : TALER_AUDITORDB_insert_purse_not_closed_inconsistencies (pg,
226 : &pnc));
227 : }
228 : /* Three rounds, one purse, one row -- carrying the latest amount. */
229 1 : FAILIF (1 !=
230 : TALER_AUDITORDB_iterate_purse_not_closed_inconsistencies (
231 : pg,
232 : 1024,
233 : 0,
234 : true,
235 : &purse_not_closed_cb,
236 : &last));
237 1 : FAILIF (0 !=
238 : TALER_amount_cmp (&last.amount,
239 : &three));
240 1 : return GNUNET_OK;
241 : }
242 :
243 :
244 : /**
245 : * Main function that will be run by the scheduler.
246 : *
247 : * @param cls closure with the configuration
248 : */
249 : static void
250 1 : run (void *cls)
251 : {
252 1 : struct GNUNET_CONFIGURATION_Handle *cfg = cls;
253 :
254 1 : if (NULL ==
255 1 : (pg = TALER_AUDITORDB_connect (cfg)))
256 : {
257 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
258 : "Failed to connect to database\n");
259 0 : result = 77;
260 0 : return;
261 : }
262 1 : GNUNET_assert (GNUNET_OK ==
263 : TALER_AUDITORDB_preflight (pg));
264 1 : (void) TALER_AUDITORDB_drop_tables (pg,
265 : GNUNET_YES);
266 1 : if (GNUNET_OK !=
267 1 : TALER_AUDITORDB_create_tables (pg,
268 : false,
269 : 0))
270 : {
271 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
272 : "Failed to 'create_tables'\n");
273 0 : result = 77;
274 0 : goto unload;
275 : }
276 1 : if (GNUNET_SYSERR ==
277 1 : TALER_AUDITORDB_preflight (pg))
278 : {
279 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
280 : "Failed preflight check\n");
281 0 : result = 77;
282 0 : goto drop;
283 : }
284 1 : if (GNUNET_OK !=
285 1 : TALER_AUDITORDB_start (pg,
286 : "test-auditordb-regression"))
287 : {
288 0 : GNUNET_break (0);
289 0 : goto drop;
290 : }
291 1 : if (GNUNET_OK !=
292 1 : test_checkpoint_upsert ())
293 0 : goto rollback;
294 1 : if (GNUNET_OK !=
295 1 : test_purse_not_closed_dedup ())
296 0 : goto rollback;
297 1 : result = 0;
298 1 : GNUNET_break (0 <=
299 : TALER_AUDITORDB_commit (pg));
300 1 : goto drop;
301 0 : rollback:
302 0 : TALER_AUDITORDB_rollback (pg);
303 1 : drop:
304 1 : GNUNET_break (GNUNET_OK ==
305 : TALER_AUDITORDB_drop_tables (pg,
306 : GNUNET_YES));
307 1 : unload:
308 1 : TALER_AUDITORDB_disconnect (pg);
309 1 : pg = NULL;
310 : }
311 :
312 :
313 : int
314 1 : main (int argc,
315 : char *const argv[])
316 : {
317 : struct GNUNET_CONFIGURATION_Handle *cfg;
318 :
319 : (void) argc;
320 1 : result = -1;
321 1 : GNUNET_log_setup (argv[0],
322 : "WARNING",
323 : NULL);
324 1 : cfg = GNUNET_CONFIGURATION_create (TALER_AUDITOR_project_data ());
325 1 : if (GNUNET_OK !=
326 1 : GNUNET_CONFIGURATION_parse (cfg,
327 : "test-auditor-db-postgres.conf"))
328 : {
329 0 : GNUNET_break (0);
330 0 : return 2;
331 : }
332 1 : GNUNET_SCHEDULER_run (&run,
333 : cfg);
334 1 : GNUNET_CONFIGURATION_destroy (cfg);
335 1 : return result;
336 : }
|