test_auditordb_regression.c (10296B)
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 amount (const char *str, 74 struct TALER_Amount *amount) 75 { 76 char buf[128]; 77 78 GNUNET_snprintf (buf, 79 sizeof (buf), 80 "%s:%s", 81 CURRENCY, 82 str); 83 GNUNET_assert (GNUNET_OK == 84 TALER_string_to_amount (buf, 85 amount)); 86 } 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 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 amount ("1", 108 &one); 109 amount ("2", 110 &two); 111 112 /* insert_balance() on a key that does not exist yet must create it. */ 113 FAILIF (0 > 114 TALER_AUDITORDB_insert_balance (pg, 115 "a1-update-only", 116 &one, 117 NULL)); 118 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 119 TALER_AUDITORDB_get_balance (pg, 120 "a1-update-only", 121 &got, 122 NULL)); 123 FAILIF (0 != 124 TALER_amount_cmp (&got, 125 &one)); 126 127 /* insert_balance() on a key that already exists must overwrite it. */ 128 FAILIF (0 > 129 TALER_AUDITORDB_insert_balance (pg, 130 "a1-insert-twice", 131 &one, 132 NULL)); 133 FAILIF (0 > 134 TALER_AUDITORDB_insert_balance (pg, 135 "a1-insert-twice", 136 &two, 137 NULL)); 138 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 139 TALER_AUDITORDB_get_balance (pg, 140 "a1-insert-twice", 141 &got, 142 NULL)); 143 FAILIF (0 != 144 TALER_amount_cmp (&got, 145 &two)); 146 147 /* Same two properties for the progress points. */ 148 FAILIF (0 > 149 TALER_AUDITORDB_insert_auditor_progress (pg, 150 "a1-update-only", 151 42, 152 NULL)); 153 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 154 TALER_AUDITORDB_get_auditor_progress (pg, 155 "a1-update-only", 156 &off, 157 NULL)); 158 FAILIF (42 != off); 159 160 FAILIF (0 > 161 TALER_AUDITORDB_insert_auditor_progress (pg, 162 "a1-insert-twice", 163 42, 164 NULL)); 165 FAILIF (0 > 166 TALER_AUDITORDB_insert_auditor_progress (pg, 167 "a1-insert-twice", 168 43, 169 NULL)); 170 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 171 TALER_AUDITORDB_get_auditor_progress (pg, 172 "a1-insert-twice", 173 &off, 174 NULL)); 175 FAILIF (43 != off); 176 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 purse_not_closed_cb ( 189 void *cls, 190 const struct TALER_AUDITORDB_PurseNotClosedInconsistencies *dc) 191 { 192 struct TALER_AUDITORDB_PurseNotClosedInconsistencies *last = cls; 193 194 *last = *dc; 195 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 test_purse_not_closed_dedup (void) 207 { 208 struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnc = { 209 .expiration_date = GNUNET_TIME_absolute_get () 210 }; 211 struct TALER_AUDITORDB_PurseNotClosedInconsistencies last = { 0 }; 212 struct TALER_Amount three; 213 214 GNUNET_CRYPTO_random_block (&pnc.purse_pub, 215 sizeof (pnc.purse_pub)); 216 amount ("1", 217 &pnc.amount); 218 amount ("3", 219 &three); 220 for (unsigned int round = 0; round < 3; round++) 221 { 222 if (2 == round) 223 pnc.amount = three; 224 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 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 FAILIF (0 != 238 TALER_amount_cmp (&last.amount, 239 &three)); 240 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 run (void *cls) 251 { 252 struct GNUNET_CONFIGURATION_Handle *cfg = cls; 253 254 if (NULL == 255 (pg = TALER_AUDITORDB_connect (cfg))) 256 { 257 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 258 "Failed to connect to database\n"); 259 result = 77; 260 return; 261 } 262 GNUNET_assert (GNUNET_OK == 263 TALER_AUDITORDB_preflight (pg)); 264 (void) TALER_AUDITORDB_drop_tables (pg, 265 GNUNET_YES); 266 if (GNUNET_OK != 267 TALER_AUDITORDB_create_tables (pg, 268 false, 269 0)) 270 { 271 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 272 "Failed to 'create_tables'\n"); 273 result = 77; 274 goto unload; 275 } 276 if (GNUNET_SYSERR == 277 TALER_AUDITORDB_preflight (pg)) 278 { 279 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 280 "Failed preflight check\n"); 281 result = 77; 282 goto drop; 283 } 284 if (GNUNET_OK != 285 TALER_AUDITORDB_start (pg, 286 "test-auditordb-regression")) 287 { 288 GNUNET_break (0); 289 goto drop; 290 } 291 if (GNUNET_OK != 292 test_checkpoint_upsert ()) 293 goto rollback; 294 if (GNUNET_OK != 295 test_purse_not_closed_dedup ()) 296 goto rollback; 297 result = 0; 298 GNUNET_break (0 <= 299 TALER_AUDITORDB_commit (pg)); 300 goto drop; 301 rollback: 302 TALER_AUDITORDB_rollback (pg); 303 drop: 304 GNUNET_break (GNUNET_OK == 305 TALER_AUDITORDB_drop_tables (pg, 306 GNUNET_YES)); 307 unload: 308 TALER_AUDITORDB_disconnect (pg); 309 pg = NULL; 310 } 311 312 313 int 314 main (int argc, 315 char *const argv[]) 316 { 317 struct GNUNET_CONFIGURATION_Handle *cfg; 318 319 (void) argc; 320 result = -1; 321 GNUNET_log_setup (argv[0], 322 "WARNING", 323 NULL); 324 cfg = GNUNET_CONFIGURATION_create (TALER_AUDITOR_project_data ()); 325 if (GNUNET_OK != 326 GNUNET_CONFIGURATION_parse (cfg, 327 "test-auditor-db-postgres.conf")) 328 { 329 GNUNET_break (0); 330 return 2; 331 } 332 GNUNET_SCHEDULER_run (&run, 333 cfg); 334 GNUNET_CONFIGURATION_destroy (cfg); 335 return result; 336 }