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_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 3 : ts (uint64_t secs)
46 : {
47 3 : struct GNUNET_TIME_Absolute abs = {
48 3 : .abs_value_us = secs * 1000LLU * 1000LLU
49 : };
50 :
51 3 : 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 2 : 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 2 : struct TALER_FullPayto payto = {
73 : .full_payto = (char *) "payto://x-taler-bank/localhost/profits"
74 : };
75 2 : struct TALER_Amount a = TDB_amount (amount);
76 : struct TALER_MasterSignatureP master_sig;
77 :
78 2 : TDB_fill (wtid,
79 : sizeof (*wtid),
80 : seed);
81 2 : TDB_FILL (master_sig,
82 : seed);
83 2 : 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 1 : 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 1 : char *account_section = NULL;
108 : uint64_t serial;
109 :
110 1 : TDB_FILL (wtid,
111 : 1);
112 1 : 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 1 : FAILIF (NULL != account_section);
122 1 : 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 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
132 : TALER_EXCHANGEDB_update_to_profit_drain_finished (pg,
133 : 1));
134 1 : 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 1 : 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 1 : struct TALER_FullPayto payto_uri = { NULL };
152 : struct GNUNET_TIME_Timestamp request_timestamp;
153 : struct TALER_Amount amount;
154 1 : struct TALER_Amount expect = TDB_amount ("10");
155 : struct TALER_MasterSignatureP master_sig;
156 : struct TALER_MasterSignatureP expect_sig;
157 1 : char *account_section = NULL;
158 1 : uint64_t serial = 0;
159 :
160 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
161 : add_drain (pg,
162 : 10,
163 : 1600000000,
164 : "10",
165 : &wtid));
166 1 : TDB_FILL (expect_sig,
167 : 10);
168 1 : 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 1 : FAILIF (0 == serial);
178 1 : FAILIF (0 != strcmp (account_section,
179 : "exchange-account-1"));
180 1 : GNUNET_free (account_section);
181 1 : FAILIF (0 != strcmp (payto_uri.full_payto,
182 : "payto://x-taler-bank/localhost/profits"));
183 1 : GNUNET_free (payto_uri.full_payto);
184 1 : FAILIF (0 != TALER_amount_cmp (&amount,
185 : &expect));
186 1 : FAILIF (GNUNET_TIME_timestamp_cmp (request_timestamp,
187 : !=,
188 : ts (1600000000)));
189 1 : FAILIF (0 != GNUNET_memcmp (&master_sig,
190 : &expect_sig));
191 :
192 : /* a wire transfer identifier we never used is not found */
193 1 : TDB_FILL (unknown,
194 : 99);
195 1 : account_section = NULL;
196 1 : 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 1 : account_section = NULL;
207 1 : 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 1 : GNUNET_free (account_section);
217 1 : GNUNET_free (payto_uri.full_payto);
218 1 : FAILIF (0 != GNUNET_memcmp (&got_wtid,
219 : &wtid));
220 1 : 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 1 : check_pending_order (struct TALER_EXCHANGEDB_PostgresContext *pg)
233 : {
234 : struct TALER_WireTransferIdentifierRawP older;
235 : struct TALER_WireTransferIdentifierRawP got_wtid;
236 1 : 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 1 : char *account_section = NULL;
241 1 : uint64_t serial = 0;
242 :
243 : /* inserted second, but with an earlier trigger date */
244 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
245 : add_drain (pg,
246 : 11,
247 : 1500000000,
248 : "3",
249 : &older));
250 1 : 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 1 : GNUNET_free (account_section);
260 1 : GNUNET_free (payto_uri.full_payto);
261 1 : FAILIF (0 != GNUNET_memcmp (&got_wtid,
262 : &older));
263 :
264 : /* draining it hands over the remaining one */
265 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
266 : TALER_EXCHANGEDB_update_to_profit_drain_finished (pg,
267 : serial));
268 1 : account_section = NULL;
269 1 : 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 1 : GNUNET_free (account_section);
279 1 : GNUNET_free (payto_uri.full_payto);
280 1 : FAILIF (0 == GNUNET_memcmp (&got_wtid,
281 : &older));
282 :
283 : /* and once both are executed, nothing is pending */
284 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
285 : TALER_EXCHANGEDB_update_to_profit_drain_finished (pg,
286 : serial));
287 1 : account_section = NULL;
288 1 : 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 1 : FAILIF (2 != TDB_count (pg,
299 : "FROM profit_drains WHERE executed"));
300 1 : 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 1 : main (int argc,
320 : char *const *argv)
321 : {
322 1 : 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 */
|