test_profit_drains.c (12435B)
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_profit_drains.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `profit_drains` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_insert_profit_drain(), 23 * #TALER_EXCHANGEDB_get_profit_drain(), 24 * #TALER_EXCHANGEDB_get_pending_profit_drain() and 25 * #TALER_EXCHANGEDB_update_to_profit_drain_finished(). 26 * 27 * `profit_drains` has no foreign keys. The pending lookup picks the 28 * oldest not-yet-executed request, so the checks insert two out of order 29 * and drain them one by one. 30 */ 31 #include "test_common.h" 32 #include "exchange-database/insert_profit_drain.h" 33 #include "exchange-database/get_profit_drain.h" 34 #include "exchange-database/get_pending_profit_drain.h" 35 #include "exchange-database/update_to_profit_drain_finished.h" 36 37 38 /** 39 * Build a timestamp from a number of seconds since the epoch. 40 * 41 * @param secs seconds since the epoch 42 * @return the timestamp 43 */ 44 static struct GNUNET_TIME_Timestamp 45 ts (uint64_t secs) 46 { 47 struct GNUNET_TIME_Absolute abs = { 48 .abs_value_us = secs * 1000LLU * 1000LLU 49 }; 50 51 return GNUNET_TIME_absolute_to_timestamp (abs); 52 } 53 54 55 /** 56 * Insert a drain request. 57 * 58 * @param pg the database context 59 * @param seed seed for the wire transfer identifier and signature 60 * @param when when was the request made 61 * @param amount amount to drain, e.g. "10" 62 * @param[out] wtid set to the wire transfer identifier used 63 * @return transaction status 64 */ 65 static enum GNUNET_DB_QueryStatus 66 add_drain (struct TALER_EXCHANGEDB_PostgresContext *pg, 67 uint32_t seed, 68 uint64_t when, 69 const char *amount, 70 struct TALER_WireTransferIdentifierRawP *wtid) 71 { 72 struct TALER_FullPayto payto = { 73 .full_payto = (char *) "payto://x-taler-bank/localhost/profits" 74 }; 75 struct TALER_Amount a = TDB_amount (amount); 76 struct TALER_MasterSignatureP master_sig; 77 78 TDB_fill (wtid, 79 sizeof (*wtid), 80 seed); 81 TDB_FILL (master_sig, 82 seed); 83 return TALER_EXCHANGEDB_insert_profit_drain (pg, 84 wtid, 85 "exchange-account-1", 86 payto, 87 ts (when), 88 &a, 89 &master_sig); 90 } 91 92 93 /** 94 * Nothing is pending and nothing is found while the table is empty. 95 * 96 * @param pg the database context 97 * @return 0 on success 98 */ 99 static int 100 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 101 { 102 struct TALER_WireTransferIdentifierRawP wtid; 103 struct TALER_FullPayto payto_uri; 104 struct GNUNET_TIME_Timestamp request_timestamp; 105 struct TALER_Amount amount; 106 struct TALER_MasterSignatureP master_sig; 107 char *account_section = NULL; 108 uint64_t serial; 109 110 TDB_FILL (wtid, 111 1); 112 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 113 TALER_EXCHANGEDB_get_profit_drain (pg, 114 &wtid, 115 &serial, 116 &account_section, 117 &payto_uri, 118 &request_timestamp, 119 &amount, 120 &master_sig)); 121 FAILIF (NULL != account_section); 122 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 123 TALER_EXCHANGEDB_get_pending_profit_drain (pg, 124 &serial, 125 &wtid, 126 &account_section, 127 &payto_uri, 128 &request_timestamp, 129 &amount, 130 &master_sig)); 131 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 132 TALER_EXCHANGEDB_update_to_profit_drain_finished (pg, 133 1)); 134 return 0; 135 } 136 137 138 /** 139 * A drain request is stored, found by its wire transfer identifier and 140 * reported as pending. 141 * 142 * @param pg the database context 143 * @return 0 on success 144 */ 145 static int 146 check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg) 147 { 148 struct TALER_WireTransferIdentifierRawP wtid; 149 struct TALER_WireTransferIdentifierRawP unknown; 150 struct TALER_WireTransferIdentifierRawP got_wtid; 151 struct TALER_FullPayto payto_uri = { NULL }; 152 struct GNUNET_TIME_Timestamp request_timestamp; 153 struct TALER_Amount amount; 154 struct TALER_Amount expect = TDB_amount ("10"); 155 struct TALER_MasterSignatureP master_sig; 156 struct TALER_MasterSignatureP expect_sig; 157 char *account_section = NULL; 158 uint64_t serial = 0; 159 160 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 161 add_drain (pg, 162 10, 163 1600000000, 164 "10", 165 &wtid)); 166 TDB_FILL (expect_sig, 167 10); 168 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 169 TALER_EXCHANGEDB_get_profit_drain (pg, 170 &wtid, 171 &serial, 172 &account_section, 173 &payto_uri, 174 &request_timestamp, 175 &amount, 176 &master_sig)); 177 FAILIF (0 == serial); 178 FAILIF (0 != strcmp (account_section, 179 "exchange-account-1")); 180 GNUNET_free (account_section); 181 FAILIF (0 != strcmp (payto_uri.full_payto, 182 "payto://x-taler-bank/localhost/profits")); 183 GNUNET_free (payto_uri.full_payto); 184 FAILIF (0 != TALER_amount_cmp (&amount, 185 &expect)); 186 FAILIF (GNUNET_TIME_timestamp_cmp (request_timestamp, 187 !=, 188 ts (1600000000))); 189 FAILIF (0 != GNUNET_memcmp (&master_sig, 190 &expect_sig)); 191 192 /* a wire transfer identifier we never used is not found */ 193 TDB_FILL (unknown, 194 99); 195 account_section = NULL; 196 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 197 TALER_EXCHANGEDB_get_profit_drain (pg, 198 &unknown, 199 &serial, 200 &account_section, 201 &payto_uri, 202 &request_timestamp, 203 &amount, 204 &master_sig)); 205 206 account_section = NULL; 207 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 208 TALER_EXCHANGEDB_get_pending_profit_drain (pg, 209 &serial, 210 &got_wtid, 211 &account_section, 212 &payto_uri, 213 &request_timestamp, 214 &amount, 215 &master_sig)); 216 GNUNET_free (account_section); 217 GNUNET_free (payto_uri.full_payto); 218 FAILIF (0 != GNUNET_memcmp (&got_wtid, 219 &wtid)); 220 return 0; 221 } 222 223 224 /** 225 * The oldest unexecuted request is the pending one, and marking it 226 * executed hands the next one over. 227 * 228 * @param pg the database context 229 * @return 0 on success 230 */ 231 static int 232 check_pending_order (struct TALER_EXCHANGEDB_PostgresContext *pg) 233 { 234 struct TALER_WireTransferIdentifierRawP older; 235 struct TALER_WireTransferIdentifierRawP got_wtid; 236 struct TALER_FullPayto payto_uri = { NULL }; 237 struct GNUNET_TIME_Timestamp request_timestamp; 238 struct TALER_Amount amount; 239 struct TALER_MasterSignatureP master_sig; 240 char *account_section = NULL; 241 uint64_t serial = 0; 242 243 /* inserted second, but with an earlier trigger date */ 244 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 245 add_drain (pg, 246 11, 247 1500000000, 248 "3", 249 &older)); 250 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 251 TALER_EXCHANGEDB_get_pending_profit_drain (pg, 252 &serial, 253 &got_wtid, 254 &account_section, 255 &payto_uri, 256 &request_timestamp, 257 &amount, 258 &master_sig)); 259 GNUNET_free (account_section); 260 GNUNET_free (payto_uri.full_payto); 261 FAILIF (0 != GNUNET_memcmp (&got_wtid, 262 &older)); 263 264 /* draining it hands over the remaining one */ 265 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 266 TALER_EXCHANGEDB_update_to_profit_drain_finished (pg, 267 serial)); 268 account_section = NULL; 269 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 270 TALER_EXCHANGEDB_get_pending_profit_drain (pg, 271 &serial, 272 &got_wtid, 273 &account_section, 274 &payto_uri, 275 &request_timestamp, 276 &amount, 277 &master_sig)); 278 GNUNET_free (account_section); 279 GNUNET_free (payto_uri.full_payto); 280 FAILIF (0 == GNUNET_memcmp (&got_wtid, 281 &older)); 282 283 /* and once both are executed, nothing is pending */ 284 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 285 TALER_EXCHANGEDB_update_to_profit_drain_finished (pg, 286 serial)); 287 account_section = NULL; 288 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 289 TALER_EXCHANGEDB_get_pending_profit_drain (pg, 290 &serial, 291 &got_wtid, 292 &account_section, 293 &payto_uri, 294 &request_timestamp, 295 &amount, 296 &master_sig)); 297 /* the executed rows are still on file */ 298 FAILIF (2 != TDB_count (pg, 299 "FROM profit_drains WHERE executed")); 300 return 0; 301 } 302 303 304 /** 305 * The checks to run, in order. 306 */ 307 static const struct TDB_Test tests[] = { 308 { "profit-drains-empty", 309 &check_empty }, 310 { "profit-drains-insert-and-lookup", 311 &check_insert_and_lookup }, 312 { "profit-drains-pending-order", 313 &check_pending_order }, 314 { NULL, NULL } 315 }; 316 317 318 int 319 main (int argc, 320 char *const *argv) 321 { 322 return TDB_main (argc, 323 argv, 324 "test-profit-drains", 325 "Tests for the exchangedb `profit_drains' table", 326 tests); 327 } 328 329 330 /* end of test_profit_drains.c */