Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2014-2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Lesser 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 src/backenddb/test_merchantdb.c
18 : * @brief testcase for merchant's postgres db plugin
19 : * @author Marcello Stanisci
20 : * @author Christian Grothoff
21 : * @author Pricilla Huang
22 : */
23 : #include "platform.h"
24 : #include "microhttpd.h"
25 : #include <taler/taler_util.h>
26 : #include <taler/taler_json_lib.h>
27 : #include <taler/taler_pq_lib.h>
28 : #include <taler/taler_dbevents.h>
29 : #include <taler/taler_signatures.h>
30 : #include "taler/taler_merchant_util.h"
31 : #include "merchantdb_lib.h"
32 : #include "helper.h"
33 : #include "merchant-database/gc.h"
34 : #include "merchant-database/start.h"
35 : #include "merchant-database/iterate_statistic_bucket_amounts.h"
36 : #include "merchant-database/iterate_statistic_bucket_counters.h"
37 : #include "merchant-database/iterate_kyc_statuses.h"
38 : #include "merchant-database/insert_kyc_failure.h"
39 : #include "merchant-database/insert_kyc_status.h"
40 : #include "merchant-database/delete_contract_terms.h"
41 : #include "merchant-database/update_to_instance_private_key_deleted.h"
42 : #include "merchant-database/delete_order.h"
43 : #include "merchant-database/delete_pending_webhook.h"
44 : #include "merchant-database/delete_product.h"
45 : #include "merchant-database/delete_template.h"
46 : #include "merchant-database/delete_webhook.h"
47 : #include "merchant-database/update_to_account_inactive.h"
48 : #include "merchant-database/do_increase_refund.h"
49 : #include "merchant-database/insert_account.h"
50 : #include "merchant-database/insert_category.h"
51 : #include "merchant-database/update_category.h"
52 : #include "merchant-database/insert_contract_terms.h"
53 : #include "merchant-database/insert_deposit.h"
54 : #include "merchant-database/insert_deposit_confirmation.h"
55 : #include "merchant-database/insert_deposit_to_transfer.h"
56 : #include "merchant-database/insert_exchange_signing_key.h"
57 : #include "merchant-database/insert_instance.h"
58 : #include "merchant-database/insert_mfa_challenge.h"
59 : #include "merchant-database/do_solve_mfa_challenge.h"
60 : #include "merchant-database/insert_order.h"
61 : #include "merchant-database/insert_order_lock.h"
62 : #include "merchant-database/insert_otp_device.h"
63 : #include "merchant-database/insert_pending_webhook.h"
64 : #include "merchant-database/insert_product.h"
65 : #include "merchant-database/insert_refund_proof.h"
66 : #include "merchant-database/insert_report.h"
67 : #include "merchant-database/insert_template.h"
68 : #include "merchant-database/insert_transfer.h"
69 : #include "merchant-database/insert_transfer_details.h"
70 : #include "merchant-database/insert_webhook.h"
71 : #include "merchant-database/insert_inventory_lock.h"
72 : #include "merchant-database/insert_issued_token.h"
73 : #include "merchant-database/insert_used_token.h"
74 : #include "merchant-database/insert_token_family.h"
75 : #include "merchant-database/insert_token_family_key.h"
76 : #include "merchant-database/get_token_family.h"
77 : #include "merchant-database/delete_token_family.h"
78 : #include "merchant-database/get_account_serial.h"
79 : #include "merchant-database/get_contract_terms.h"
80 : #include "merchant-database/get_contract_terms_status.h"
81 : #include "merchant-database/iterate_deposits.h"
82 : #include "merchant-database/iterate_deposits_by_contract_and_coin.h"
83 : #include "merchant-database/iterate_deposits_by_order.h"
84 : #include "merchant-database/iterate_instances.h"
85 : #include "merchant-database/get_order.h"
86 : #include "merchant-database/get_order_by_fulfillment.h"
87 : #include "merchant-database/get_order_status.h"
88 : #include "merchant-database/iterate_orders.h"
89 : #include "merchant-database/iterate_pending_deposits.h"
90 : #include "merchant-database/iterate_pending_webhooks_above_serial_id.h"
91 : #include "merchant-database/iterate_pending_webhooks.h"
92 : #include "merchant-database/get_product.h"
93 : #include "merchant-database/iterate_products.h"
94 : #include "merchant-database/get_refund_proof.h"
95 : #include "merchant-database/iterate_refunds.h"
96 : #include "merchant-database/iterate_refunds_detailed.h"
97 : #include "merchant-database/get_template.h"
98 : #include "merchant-database/iterate_templates.h"
99 : #include "merchant-database/iterate_transfer_details.h"
100 : #include "merchant-database/iterate_transfer_details_by_order.h"
101 : #include "merchant-database/iterate_transfer_summaries.h"
102 : #include "merchant-database/iterate_transfers.h"
103 : #include "merchant-database/get_webhook.h"
104 : #include "merchant-database/iterate_webhooks_by_event.h"
105 : #include "merchant-database/iterate_webhooks.h"
106 : #include "merchant-database/get_exchange_wire_fee.h"
107 : #include "merchant-database/get_instance_auth.h"
108 : #include "merchant-database/update_to_contract_terms_paid.h"
109 : #include "merchant-database/update_to_contract_terms_wired.h"
110 : #include "merchant-database/insert_refund_by_coin.h"
111 : #include "merchant-database/set_instance.h"
112 : #include "merchant-database/insert_exchange_wire_fee.h"
113 : #include "merchant-database/delete_inventory_lock.h"
114 : #include "merchant-database/update_contract_terms.h"
115 : #include "merchant-database/update_instance.h"
116 : #include "merchant-database/update_pending_webhook.h"
117 : #include "merchant-database/update_product.h"
118 : #include "merchant-database/update_template.h"
119 : #include "merchant-database/update_webhook.h"
120 : #include "merchant-database/create_tables.h"
121 : #include "merchant-database/drop_tables.h"
122 : #include "merchant-database/event_listen.h"
123 : #include "merchant-database/preflight.h"
124 : #include "merchant-database/delete_instance.h"
125 :
126 :
127 : /**
128 : * Global return value for the test. Initially -1, set to 0 upon
129 : * completion. Other values indicate some kind of error.
130 : */
131 : static int result;
132 :
133 : /**
134 : * Handle to the database we are testing.
135 : */
136 : static struct TALER_MERCHANTDB_PostgresContext *pg;
137 :
138 : /**
139 : * Configuration of the database we are testing. Used by tests that
140 : * need a second, concurrent connection.
141 : */
142 : static const struct GNUNET_CONFIGURATION_Handle *test_cfg;
143 :
144 : /**
145 : * @param test 0 on success, non-zero on failure
146 : */
147 : #define TEST_WITH_FAIL_CLAUSE(test, on_fail) \
148 : if ((test)) \
149 : { \
150 : GNUNET_break (0); \
151 : on_fail \
152 : }
153 :
154 : #define TEST_COND_RET_ON_FAIL(cond, msg) \
155 : if (! (cond)) \
156 : { \
157 : GNUNET_break (0); \
158 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
159 : msg); \
160 : return 1; \
161 : }
162 :
163 : /**
164 : * @param __test 0 on success, non-zero on failure
165 : */
166 : #define TEST_RET_ON_FAIL(__test) \
167 : TEST_WITH_FAIL_CLAUSE (__test, \
168 : return 1; \
169 : )
170 :
171 : /**
172 : * Activate the per-instance schema for @a iid before invoking any
173 : * per-instance MERCHANTDB function. Returns 1 from the enclosing
174 : * test helper if routing fails for an unexpected reason.
175 : *
176 : * If @a iid does not resolve to an existing instance, set_instance
177 : * returns NO_RESULTS. In that case the underlying per-instance call
178 : * could never have run, so we treat it as the test outcome: when the
179 : * caller expected NO_RESULTS, return 0 (pass); otherwise return 1.
180 : *
181 : * @param iid C-string instance identifier
182 : * @param expected expected GNUNET_DB_QueryStatus from the wrapped call
183 : */
184 : #define TEST_SET_INSTANCE(iid, expected) \
185 : do { \
186 : enum GNUNET_DB_QueryStatus _si_qs; \
187 : _si_qs = TALER_MERCHANTDB_set_instance (pg, (iid)); \
188 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == _si_qs) \
189 : { \
190 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == (expected)) \
191 : return 0; \
192 : GNUNET_break (0); \
193 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
194 : "set_instance(%s): instance missing, " \
195 : "expected qs=%d\n", \
196 : (iid), \
197 : (int) (expected)); \
198 : return 1; \
199 : } \
200 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != _si_qs) \
201 : { \
202 : GNUNET_break (0); \
203 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
204 : "set_instance(%s) failed (qs=%d)\n", \
205 : (iid), \
206 : (int) _si_qs); \
207 : return 1; \
208 : } \
209 : } while (0)
210 :
211 :
212 : /**
213 : * Runs @a sql directly on the database connection of the test. Used to
214 : * set up or inspect state that has no dedicated MERCHANTDB entry point,
215 : * such as the notification settings of an instance or the rows queued by
216 : * a trigger.
217 : *
218 : * @param sql the SQL statement(s) to execute
219 : * @return 0 on success, 1 otherwise.
220 : */
221 : static int
222 2 : exec_sql (const char *sql)
223 : {
224 2 : struct GNUNET_PQ_ExecuteStatement es[] = {
225 2 : GNUNET_PQ_make_execute (sql),
226 : GNUNET_PQ_EXECUTE_STATEMENT_END
227 : };
228 :
229 2 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
230 : GNUNET_PQ_exec_statements (pg->conn,
231 : es),
232 : "Direct SQL execution failed\n");
233 2 : return 0;
234 : }
235 :
236 :
237 : /**
238 : * Evaluates @a sql, which must be a parameter-less query returning
239 : * exactly one row with a single INT8 column named "num".
240 : *
241 : * @param sql the query to run
242 : * @param[out] num set to the value returned
243 : * @return 0 on success, 1 otherwise.
244 : */
245 : static int
246 6 : query_sql_num (const char *sql,
247 : uint64_t *num)
248 : {
249 6 : struct GNUNET_PQ_QueryParam params[] = {
250 : GNUNET_PQ_query_param_end
251 : };
252 6 : struct GNUNET_PQ_ResultSpec rs[] = {
253 6 : GNUNET_PQ_result_spec_uint64 ("num",
254 : num),
255 : GNUNET_PQ_result_spec_end
256 : };
257 :
258 6 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
259 : GNUNET_PQ_prepare_anon (pg->conn,
260 : sql),
261 : "Preparing direct query failed\n");
262 6 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
263 : GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
264 : "",
265 : params,
266 : rs),
267 : "Direct query failed\n");
268 6 : return 0;
269 : }
270 :
271 :
272 : /**
273 : * Evaluates @a sql, which must be a parameter-less query returning
274 : * exactly one row with a single TEXT column named "txt".
275 : *
276 : * @param sql the query to run
277 : * @param[out] txt set to the value returned, to be freed by the caller
278 : * @return 0 on success, 1 otherwise.
279 : */
280 : static int
281 1 : query_sql_text (const char *sql,
282 : char **txt)
283 : {
284 1 : struct GNUNET_PQ_QueryParam params[] = {
285 : GNUNET_PQ_query_param_end
286 : };
287 1 : struct GNUNET_PQ_ResultSpec rs[] = {
288 1 : GNUNET_PQ_result_spec_string ("txt",
289 : txt),
290 : GNUNET_PQ_result_spec_end
291 : };
292 :
293 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
294 : GNUNET_PQ_prepare_anon (pg->conn,
295 : sql),
296 : "Preparing direct query failed\n");
297 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
298 : GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
299 : "",
300 : params,
301 : rs),
302 : "Direct query failed\n");
303 1 : return 0;
304 : }
305 :
306 :
307 : /* ********** Instances ********** */
308 :
309 :
310 : /**
311 : * Container for instance settings along with keys.
312 : */
313 : struct InstanceData
314 : {
315 : /**
316 : * The instance settings.
317 : */
318 : struct TALER_MERCHANTDB_InstanceSettings instance;
319 :
320 : /**
321 : * The public key for the instance.
322 : */
323 : struct TALER_MerchantPublicKeyP merchant_pub;
324 :
325 : /**
326 : * The private key for the instance.
327 : */
328 : struct TALER_MerchantPrivateKeyP merchant_priv;
329 : };
330 :
331 :
332 : /**
333 : * Creates data for an instance to use with testing.
334 : *
335 : * @param instance_id the identifier for this instance.
336 : * @param instance the instance data to be filled.
337 : */
338 : static void
339 16 : make_instance (const char *instance_id,
340 : struct InstanceData *instance)
341 : {
342 16 : memset (instance,
343 : 0,
344 : sizeof (*instance));
345 16 : GNUNET_CRYPTO_eddsa_key_create (&instance->merchant_priv.eddsa_priv);
346 16 : GNUNET_CRYPTO_eddsa_key_get_public (&instance->merchant_priv.eddsa_priv,
347 : &instance->merchant_pub.eddsa_pub);
348 16 : instance->instance.id = (char *) instance_id;
349 16 : instance->instance.name = (char *) "Test";
350 16 : instance->instance.address = json_array ();
351 16 : GNUNET_assert (NULL != instance->instance.address);
352 16 : GNUNET_assert (0 == json_array_append_new (instance->instance.address,
353 : json_string ("123 Example St")));
354 16 : instance->instance.jurisdiction = json_array ();
355 16 : GNUNET_assert (NULL != instance->instance.jurisdiction);
356 16 : GNUNET_assert (0 == json_array_append_new (instance->instance.jurisdiction,
357 : json_string ("Ohio")));
358 16 : instance->instance.use_stefan = true;
359 : instance->instance.default_wire_transfer_delay =
360 16 : GNUNET_TIME_relative_get_minute_ ();
361 16 : instance->instance.default_pay_delay = GNUNET_TIME_UNIT_SECONDS;
362 16 : instance->instance.default_refund_delay = GNUNET_TIME_UNIT_MINUTES;
363 16 : }
364 :
365 :
366 : /**
367 : * Frees memory allocated when creating an instance for testing.
368 : *
369 : * @param instance the instance containing the memory to be freed.
370 : */
371 : static void
372 15 : free_instance_data (struct InstanceData *instance)
373 : {
374 15 : json_decref (instance->instance.address);
375 15 : json_decref (instance->instance.jurisdiction);
376 15 : }
377 :
378 :
379 : /**
380 : * Creates an account with test data for an instance.
381 : *
382 : * @param account the account to initialize.
383 : */
384 : static void
385 8 : make_account (struct TALER_MERCHANTDB_AccountDetails *account)
386 : {
387 8 : memset (account,
388 : 0,
389 : sizeof (*account));
390 8 : GNUNET_CRYPTO_random_block (&account->h_wire.hash,
391 : sizeof account->h_wire.hash);
392 8 : GNUNET_CRYPTO_random_block (&account->salt,
393 : sizeof (account->salt));
394 : account->payto_uri.full_payto
395 8 : = (char *) "payto://x-taler-bank/bank.demo.taler.net/4";
396 8 : account->active = true;
397 8 : }
398 :
399 :
400 : /**
401 : * Instance settings along with corresponding accounts.
402 : */
403 : struct InstanceWithAccounts
404 : {
405 : /**
406 : * Pointer to the instance settings.
407 : */
408 : const struct TALER_MERCHANTDB_InstanceSettings *instance;
409 :
410 : /**
411 : * Length of the array of accounts.
412 : */
413 : unsigned int accounts_length;
414 :
415 : /**
416 : * Pointer to the array of accounts.
417 : */
418 : const struct TALER_MERCHANTDB_AccountDetails *accounts;
419 :
420 : };
421 :
422 :
423 : /**
424 : * Closure for testing instance lookup.
425 : */
426 : struct TestLookupInstances_Closure
427 : {
428 : /**
429 : * Number of instances to compare to.
430 : */
431 : unsigned int instances_to_cmp_length;
432 :
433 : /**
434 : * Pointer to array of instances.
435 : */
436 : const struct InstanceWithAccounts *instances_to_cmp;
437 :
438 : /**
439 : * Pointer to array of number of matches for each instance.
440 : */
441 : unsigned int *results_matching;
442 :
443 : /**
444 : * Total number of results returned.
445 : */
446 : unsigned int results_length;
447 : };
448 :
449 :
450 : /**
451 : * Compares two instances for equality.
452 : *
453 : * @param a the first instance.
454 : * @param b the second instance.
455 : * @return 0 on equality, 1 otherwise.
456 : */
457 : static int
458 11 : check_instances_equal (const struct TALER_MERCHANTDB_InstanceSettings *a,
459 : const struct TALER_MERCHANTDB_InstanceSettings *b)
460 : {
461 11 : if ((0 != strcmp (a->id,
462 11 : b->id)) ||
463 9 : (0 != strcmp (a->name,
464 18 : b->name)) ||
465 9 : (1 != json_equal (a->address,
466 18 : b->address)) ||
467 9 : (1 != json_equal (a->jurisdiction,
468 9 : b->jurisdiction)) ||
469 9 : (a->use_stefan != b->use_stefan) ||
470 9 : (a->default_wire_transfer_delay.rel_value_us !=
471 9 : b->default_wire_transfer_delay.rel_value_us) ||
472 9 : (a->default_pay_delay.rel_value_us != b->default_pay_delay.rel_value_us))
473 2 : return 1;
474 9 : return 0;
475 : }
476 :
477 :
478 : #if 0
479 : /**
480 : * Compares two accounts for equality.
481 : *
482 : * @param a the first account.
483 : * @param b the second account.
484 : * @return 0 on equality, 1 otherwise.
485 : */
486 : static int
487 : check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a,
488 : const struct TALER_MERCHANTDB_AccountDetails *b)
489 : {
490 : if ((0 != GNUNET_memcmp (&a->h_wire,
491 : &b->h_wire)) ||
492 : (0 != GNUNET_memcmp (&a->salt,
493 : &b->salt)) ||
494 : (0 != TALER_full_payto_cmp (a->payto_uri,
495 : b->payto_uri)) ||
496 : (a->active != b->active))
497 : return 1;
498 : return 0;
499 : }
500 :
501 :
502 : #endif
503 :
504 :
505 : /**
506 : * Called after testing 'iterate_instances'.
507 : *
508 : * @param cls pointer to 'struct TestLookupInstances_Closure'.
509 : * @param merchant_pub public key of the instance
510 : * @param merchant_priv private key of the instance, NULL if not available
511 : * @param is general instance settings
512 : * @param ias instance authentication settings
513 : */
514 : static void
515 9 : lookup_instances_cb (void *cls,
516 : const struct TALER_MerchantPublicKeyP *merchant_pub,
517 : const struct TALER_MerchantPrivateKeyP *merchant_priv,
518 : const struct TALER_MERCHANTDB_InstanceSettings *is,
519 : const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
520 : {
521 9 : struct TestLookupInstances_Closure *cmp = cls;
522 :
523 9 : if (NULL == cmp)
524 0 : return;
525 9 : cmp->results_length += 1;
526 : /* Look through the closure and test each instance for equality */
527 20 : for (unsigned int i = 0; cmp->instances_to_cmp_length > i; ++i)
528 : {
529 11 : if (0 != check_instances_equal (cmp->instances_to_cmp[i].instance,
530 : is))
531 2 : continue;
532 9 : cmp->results_matching[i] += 1;
533 : }
534 : }
535 :
536 :
537 : /**
538 : * Tests @e insert_instance.
539 : *
540 : * @param instance the instance data to insert.
541 : * @param expected_result the result that should be returned from the DB.
542 : * @return 0 on success, 1 on failure.
543 : */
544 : static int
545 17 : test_insert_instance (const struct InstanceData *instance,
546 : enum GNUNET_DB_QueryStatus expected_result)
547 : {
548 17 : struct TALER_MERCHANTDB_InstanceAuthSettings ias = { 0 };
549 :
550 17 : TEST_COND_RET_ON_FAIL (expected_result ==
551 : TALER_MERCHANTDB_insert_instance (pg,
552 : &instance->merchant_pub,
553 : &instance->merchant_priv,
554 : &instance->instance,
555 : &ias),
556 : "Insert instance failed\n");
557 17 : return 0;
558 : }
559 :
560 :
561 : /**
562 : * Tests @e update_instance.
563 : *
564 : * @param updated_data the instance data to update the row in the database to.
565 : * @param expected_result the result that should be returned from the DB.
566 : * @return 0 on success, 1 on failure.
567 : */
568 : static int
569 2 : test_update_instance (const struct InstanceData *updated_data,
570 : enum GNUNET_DB_QueryStatus expected_result)
571 : {
572 2 : TEST_COND_RET_ON_FAIL (expected_result ==
573 : TALER_MERCHANTDB_update_instance (pg,
574 : &updated_data->instance),
575 : "Update instance failed\n");
576 2 : return 0;
577 : }
578 :
579 :
580 : /**
581 : * Tests @e iterate_instances.
582 : *
583 : * @param active_only whether to lookup all instance, or only active ones.
584 : * @param instances_length number of instances to compare to in @e instances.
585 : * @param instances a list of instances that will be compared with the results
586 : * found.
587 : * @return 0 on success, 1 otherwise.
588 : */
589 : static int
590 9 : test_lookup_instances (bool active_only,
591 : unsigned int instances_length,
592 : struct InstanceWithAccounts instances[])
593 9 : {
594 9 : unsigned int results_matching[GNUNET_NZL (instances_length)];
595 9 : struct TestLookupInstances_Closure cmp = {
596 : .instances_to_cmp_length = instances_length,
597 : .instances_to_cmp = instances,
598 : .results_matching = results_matching,
599 : .results_length = 0
600 : };
601 9 : memset (results_matching, 0, sizeof (unsigned int) * instances_length);
602 9 : if (0 > TALER_MERCHANTDB_iterate_instances (pg,
603 : active_only,
604 : &lookup_instances_cb,
605 : &cmp))
606 : {
607 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
608 : "Lookup instances failed\n");
609 0 : return 1;
610 : }
611 9 : if (instances_length != cmp.results_length)
612 : {
613 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
614 : "Lookup instances failed: incorrect number of results\n");
615 0 : return 1;
616 : }
617 18 : for (unsigned int i = 0; instances_length > i; ++i)
618 : {
619 9 : if (1 != cmp.results_matching[i])
620 : {
621 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
622 : "Lookup instances failed: mismatched data\n");
623 0 : return 1;
624 : }
625 : }
626 9 : return 0;
627 : }
628 :
629 :
630 : /**
631 : * Tests removing the private key of the given instance from the database.
632 : *
633 : * @param instance the instance whose private key to delete.
634 : * @param expected_result the result we expect the db to return.
635 : * @return 0 on success, 1 otherwise.
636 : */
637 : static int
638 3 : test_delete_instance_private_key (const struct InstanceData *instance,
639 : enum GNUNET_DB_QueryStatus expected_result)
640 : {
641 3 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
642 2 : TEST_COND_RET_ON_FAIL (expected_result ==
643 : TALER_MERCHANTDB_update_to_instance_private_key_deleted (
644 : pg,
645 : instance->instance.id),
646 : "Delete instance private key failed\n");
647 2 : return 0;
648 : }
649 :
650 :
651 : /**
652 : * Tests purging all data for an instance from the database.
653 : *
654 : * @param instance the instance to purge.
655 : * @param expected_result the result we expect the db to return.
656 : * @return 0 on success, 1 otherwise.
657 : */
658 : static int
659 2 : test_purge_instance (const struct InstanceData *instance,
660 : enum GNUNET_DB_QueryStatus expected_result)
661 : {
662 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
663 2 : TEST_COND_RET_ON_FAIL (expected_result ==
664 : TALER_MERCHANTDB_delete_instance (pg,
665 : instance->instance.id),
666 : "Purge instance failed\n");
667 2 : return 0;
668 : }
669 :
670 :
671 : /**
672 : * Tests inserting an account for a merchant instance.
673 : *
674 : * @param instance the instance to associate the account with.
675 : * @param account the account to insert.
676 : * @param expected_result the result expected from the db.
677 : * @return 0 on success, 1 otherwise.
678 : */
679 : static int
680 9 : test_insert_account (const struct InstanceData *instance,
681 : const struct TALER_MERCHANTDB_AccountDetails *account,
682 : enum GNUNET_DB_QueryStatus expected_result)
683 : {
684 9 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
685 8 : TEST_COND_RET_ON_FAIL (expected_result ==
686 : TALER_MERCHANTDB_insert_account (pg,
687 : account),
688 : "Insert account failed\n");
689 8 : return 0;
690 : }
691 :
692 :
693 : /**
694 : * Tests deactivating an account.
695 : *
696 : * @param account the account to deactivate.
697 : * @param expected_result the result expected from the DB.
698 : * @return 0 on success, 1 otherwise.
699 : */
700 : static int
701 2 : test_inactivate_account (const struct InstanceData *instance,
702 : const struct TALER_MERCHANTDB_AccountDetails *account,
703 : enum GNUNET_DB_QueryStatus expected_result)
704 : {
705 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
706 1 : TEST_COND_RET_ON_FAIL (expected_result ==
707 : TALER_MERCHANTDB_update_to_account_inactive (pg,
708 : instance->instance.id,
709 : &account->h_wire),
710 : "Deactivate account failed\n");
711 1 : return 0;
712 : }
713 :
714 :
715 : /**
716 : * Closure for instance tests
717 : */
718 : struct TestInstances_Closure
719 : {
720 : /**
721 : * The list of instances that we use for testing instances.
722 : */
723 : struct InstanceData instances[2];
724 :
725 : /**
726 : * The list of accounts to use with the instances.
727 : */
728 : struct TALER_MERCHANTDB_AccountDetails accounts[2];
729 :
730 : };
731 :
732 :
733 : /**
734 : * Sets up the data structures used in the instance tests
735 : *
736 : * @cls the closure to initialize with test data.
737 : */
738 : static void
739 1 : pre_test_instances (struct TestInstances_Closure *cls)
740 : {
741 : /* Instance */
742 1 : make_instance ("test_instances_inst0",
743 : &cls->instances[0]);
744 1 : make_instance ("test_instances_inst1",
745 : &cls->instances[1]);
746 :
747 : /* Accounts */
748 1 : make_account (&cls->accounts[0]);
749 : cls->accounts[0].instance_id
750 1 : = cls->instances[0].instance.id;
751 1 : make_account (&cls->accounts[1]);
752 : cls->accounts[1].instance_id
753 1 : = cls->instances[1].instance.id;
754 1 : }
755 :
756 :
757 : /**
758 : * Handles all teardown after testing
759 : *
760 : * @cls the closure to free data from
761 : */
762 : static void
763 1 : post_test_instances (struct TestInstances_Closure *cls)
764 : {
765 1 : free_instance_data (&cls->instances[0]);
766 1 : free_instance_data (&cls->instances[1]);
767 1 : }
768 :
769 :
770 : /**
771 : * Tests that a per-instance operation still resolves against the
772 : * per-instance schema after the database connection was dropped and
773 : * re-established.
774 : *
775 : * Regression test: reconnect_cb() re-issues 'SET search_path TO
776 : * merchant' but used to leave @e current_merchant_id set, so the
777 : * TALER_MERCHANTDB_set_instance() the dispatcher does for the next
778 : * request found the instance already selected and returned without
779 : * re-issuing the per-instance search_path. Every unqualified
780 : * statement of that instance then hit schema 'merchant' and failed
781 : * with 42P01 (HTTP 500) until a different instance was touched.
782 : *
783 : * @param instance the instance to run the test against.
784 : * @return 0 when successful, 1 otherwise.
785 : */
786 : static int
787 1 : test_reconnect_search_path (const struct InstanceData *instance)
788 : {
789 1 : json_t *i18n = json_object ();
790 : struct TALER_MERCHANTDB_PostgresContext *cpg;
791 : uint64_t backend_pid;
792 : uint64_t gen;
793 : uint64_t cat;
794 : char sql[128];
795 1 : int ret = 1;
796 :
797 1 : GNUNET_assert (NULL != i18n);
798 1 : TEST_SET_INSTANCE (instance->instance.id,
799 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
800 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
801 1 : TALER_MERCHANTDB_insert_category (pg,
802 1 : instance->instance.id,
803 : "reconnect-before",
804 : i18n,
805 : &cat))
806 : {
807 0 : GNUNET_break (0);
808 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
809 : "Insert category before reconnect failed\n");
810 0 : goto cleanup;
811 : }
812 : /* Find out which backend serves our connection. */
813 : {
814 1 : struct GNUNET_PQ_QueryParam params[] = {
815 : GNUNET_PQ_query_param_end
816 : };
817 1 : struct GNUNET_PQ_ResultSpec rs[] = {
818 1 : GNUNET_PQ_result_spec_uint64 ("pid",
819 : &backend_pid),
820 : GNUNET_PQ_result_spec_end
821 : };
822 :
823 1 : if ( (GNUNET_OK !=
824 1 : GNUNET_PQ_prepare_anon (pg->conn,
825 1 : "SELECT pg_backend_pid()::INT8 AS pid")) ||
826 : (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
827 1 : GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
828 : "",
829 : params,
830 : rs)) )
831 : {
832 0 : GNUNET_break (0);
833 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
834 : "Failed to determine backend PID\n");
835 0 : goto cleanup;
836 : }
837 : }
838 : /* Kill our own backend from a second connection, the way a
839 : PostgreSQL restart or a pgbouncer recycle would. */
840 1 : GNUNET_snprintf (sql,
841 : sizeof (sql),
842 : "DO $$ BEGIN"
843 : " PERFORM pg_terminate_backend(%llu);"
844 : " END $$",
845 : (unsigned long long) backend_pid);
846 1 : cpg = TALER_MERCHANTDB_connect (test_cfg);
847 1 : if (NULL == cpg)
848 : {
849 0 : GNUNET_break (0);
850 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
851 : "Failed to open second database connection\n");
852 0 : goto cleanup;
853 : }
854 : {
855 1 : struct GNUNET_PQ_ExecuteStatement es[] = {
856 1 : GNUNET_PQ_make_execute (sql),
857 : GNUNET_PQ_EXECUTE_STATEMENT_END
858 : };
859 : enum GNUNET_GenericReturnValue res;
860 :
861 1 : res = GNUNET_PQ_exec_statements (cpg->conn,
862 : es);
863 1 : TALER_MERCHANTDB_disconnect (cpg);
864 1 : if (GNUNET_OK != res)
865 : {
866 0 : GNUNET_break (0);
867 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
868 : "Failed to terminate our own backend\n");
869 0 : goto cleanup;
870 : }
871 : }
872 1 : gen = TMH_PG_prep_gen_;
873 2 : for (unsigned int i = 0; i<10; i++)
874 : {
875 2 : GNUNET_PQ_reconnect_if_down (pg->conn);
876 2 : if (gen != TMH_PG_prep_gen_)
877 1 : break;
878 1 : sleep (1);
879 : }
880 1 : if (gen == TMH_PG_prep_gen_)
881 : {
882 0 : GNUNET_break (0);
883 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
884 : "Database did not reconnect\n");
885 0 : goto cleanup;
886 : }
887 : /* This is what the dispatcher does for the next request; it must
888 : restore the per-instance search_path. */
889 1 : TEST_SET_INSTANCE (instance->instance.id,
890 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
891 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
892 1 : TALER_MERCHANTDB_insert_category (pg,
893 1 : instance->instance.id,
894 : "reconnect-after",
895 : i18n,
896 : &cat))
897 : {
898 0 : GNUNET_break (0);
899 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
900 : "Insert category after reconnect failed\n");
901 0 : goto cleanup;
902 : }
903 1 : ret = 0;
904 1 : cleanup:
905 1 : json_decref (i18n);
906 1 : return ret;
907 : }
908 :
909 :
910 : /**
911 : * Tests that an instance whose 'auth_hash'/'auth_salt' are NULL is
912 : * read back as "no password configured" instead of failing.
913 : *
914 : * Both columns are nullable, but the result spec did not allow NULL,
915 : * so such a row made TALER_MERCHANTDB_get_instance_auth() return a
916 : * hard error (HTTP 500 on every authenticated request of that
917 : * instance). No code path in the tree writes NULL today -- the
918 : * "no password" state is an all-zero hash -- so the NULL is set here
919 : * directly via SQL.
920 : *
921 : * @param instance the instance to run the test against.
922 : * @return 0 on success, 1 otherwise.
923 : */
924 : static int
925 1 : test_get_instance_auth_null (const struct InstanceData *instance)
926 : {
927 : struct TALER_MERCHANTDB_InstanceAuthSettings ias;
928 : char sql[256];
929 :
930 1 : GNUNET_snprintf (sql,
931 : sizeof (sql),
932 : "UPDATE merchant.merchant_instances"
933 : " SET auth_hash=NULL"
934 : " ,auth_salt=NULL"
935 : " WHERE merchant_id='%s'",
936 1 : instance->instance.id);
937 : {
938 1 : struct GNUNET_PQ_ExecuteStatement es[] = {
939 1 : GNUNET_PQ_make_execute (sql),
940 : GNUNET_PQ_EXECUTE_STATEMENT_END
941 : };
942 :
943 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
944 : GNUNET_PQ_exec_statements (pg->conn,
945 : es),
946 : "Failed to NULL out the instance authentication\n");
947 : }
948 1 : memset (&ias,
949 : 42,
950 : sizeof (ias));
951 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
952 : TALER_MERCHANTDB_get_instance_auth (
953 : pg,
954 : instance->instance.id,
955 : &ias),
956 : "Lookup of NULL instance authentication failed\n");
957 1 : TEST_COND_RET_ON_FAIL (GNUNET_is_zero (&ias.auth_hash) &&
958 : GNUNET_is_zero (&ias.auth_salt),
959 : "NULL instance authentication was not zeroed out\n");
960 1 : return 0;
961 : }
962 :
963 :
964 : /**
965 : * Tests that storing a report always yields a fresh serial.
966 : *
967 : * 'merchant_reports' has no unique key other than the identity
968 : * column 'report_serial' that the INSERT never supplies, so the
969 : * 'ON CONFLICT DO NOTHING' the INSERT used to carry could never
970 : * fire. Should a deduplicating key ever be introduced, the INSERT
971 : * must report it (as 'no results') rather than silently return no
972 : * serial: POST /private/reports would otherwise answer 200 OK with
973 : * an uninitialised 'report_serial_id'.
974 : *
975 : * @param instance the instance to store the reports for.
976 : * @return 0 on success, 1 otherwise.
977 : */
978 : static int
979 1 : test_insert_report_serial (const struct InstanceData *instance)
980 : {
981 : uint64_t report_ids[3];
982 :
983 1 : TEST_SET_INSTANCE (instance->instance.id,
984 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
985 4 : for (unsigned int i = 0; i<3; i++)
986 : {
987 3 : report_ids[i] = 0;
988 3 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
989 : TALER_MERCHANTDB_insert_report (
990 : pg,
991 : instance->instance.id,
992 : "report-generator-test",
993 : "a test report",
994 : "text/plain",
995 : "/private/orders",
996 : "test@example.com",
997 : GNUNET_TIME_UNIT_DAYS,
998 : GNUNET_TIME_UNIT_ZERO,
999 : &report_ids[i]),
1000 : "Insert report failed\n");
1001 3 : TEST_COND_RET_ON_FAIL (0 != report_ids[i],
1002 : "Insert report did not return a serial\n");
1003 6 : for (unsigned int j = 0; j<i; j++)
1004 3 : TEST_COND_RET_ON_FAIL (report_ids[i] != report_ids[j],
1005 : "Insert report returned a duplicate serial\n");
1006 : }
1007 1 : return 0;
1008 : }
1009 :
1010 :
1011 : /**
1012 : * Function that tests instances.
1013 : *
1014 : * @param cls closure with config
1015 : * @return 0 on success, 1 if failure.
1016 : */
1017 : static int
1018 1 : run_test_instances (struct TestInstances_Closure *cls)
1019 : {
1020 1 : struct InstanceWithAccounts instances[2] = {
1021 : {
1022 : .accounts_length = 0,
1023 1 : .accounts = cls->accounts,
1024 1 : .instance = &cls->instances[0].instance
1025 : },
1026 : {
1027 : .accounts_length = 0,
1028 1 : .accounts = cls->accounts,
1029 1 : .instance = &cls->instances[1].instance
1030 : }
1031 : };
1032 : uint64_t account_serial;
1033 :
1034 : /* Test inserting an instance */
1035 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0],
1036 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
1037 : /* Test double insertion fails */
1038 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0],
1039 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
1040 : /* Test lookup instances- is our new instance there? */
1041 1 : TEST_RET_ON_FAIL (test_lookup_instances (false,
1042 : 1,
1043 : instances));
1044 : /* Test update instance */
1045 1 : cls->instances[0].instance.name = "Test - updated";
1046 1 : json_array_append_new (cls->instances[0].instance.address,
1047 : json_pack ("{s:s, s:I}",
1048 : "this", "is",
1049 : "more data", 47));
1050 1 : json_array_append_new (cls->instances[0].instance.jurisdiction,
1051 : json_pack ("{s:s}",
1052 : "vegetables", "bad"));
1053 1 : cls->instances[0].instance.use_stefan = false;
1054 : cls->instances[0].instance.default_wire_transfer_delay =
1055 1 : GNUNET_TIME_UNIT_HOURS;
1056 1 : cls->instances[0].instance.default_pay_delay = GNUNET_TIME_UNIT_MINUTES;
1057 :
1058 1 : TEST_RET_ON_FAIL (test_update_instance (&cls->instances[0],
1059 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
1060 1 : TEST_RET_ON_FAIL (test_lookup_instances (false,
1061 : 1,
1062 : instances));
1063 1 : TEST_RET_ON_FAIL (test_update_instance (&cls->instances[1],
1064 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
1065 : /* Test account creation */
1066 1 : TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0],
1067 : &cls->accounts[0],
1068 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
1069 : /* Test double account insertion fails */
1070 1 : TEST_RET_ON_FAIL (test_insert_account (&cls->instances[1],
1071 : &cls->accounts[1],
1072 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
1073 1 : TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0],
1074 : &cls->accounts[0],
1075 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
1076 1 : instances[0].accounts_length = 1;
1077 1 : TEST_RET_ON_FAIL (test_lookup_instances (false,
1078 : 1,
1079 : instances));
1080 : /* Test deactivate account */
1081 1 : TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[0],
1082 : &cls->accounts[0],
1083 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
1084 1 : TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[1],
1085 : &cls->accounts[1],
1086 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
1087 1 : cls->accounts[0].active = false;
1088 1 : TEST_RET_ON_FAIL (test_lookup_instances (false,
1089 : 1,
1090 : instances));
1091 : /* Test lookup account */
1092 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1093 1 : TALER_MERCHANTDB_get_account_serial (pg,
1094 1 : cls->instances[0].instance.id,
1095 : cls->accounts[0].payto_uri,
1096 : &account_serial))
1097 : {
1098 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1099 : "Lookup account failed\n");
1100 0 : return 1;
1101 : }
1102 1 : if (1 != account_serial)
1103 : {
1104 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1105 : "Lookup account failed: incorrect serial number found\n");
1106 0 : return 1;
1107 : }
1108 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
1109 1 : TALER_MERCHANTDB_get_account_serial (pg,
1110 1 : cls->instances[0].instance.id,
1111 1 : (struct TALER_FullPayto) {
1112 : (char *) "payto://other-uri"
1113 : },
1114 : &account_serial))
1115 : {
1116 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1117 : "Lookup account failed: account found where there is none\n");
1118 0 : return 1;
1119 : }
1120 : /* Test that a reconnect does not lose the per-instance search_path */
1121 1 : TEST_RET_ON_FAIL (test_reconnect_search_path (&cls->instances[0]));
1122 : /* Test that every stored report gets its own serial */
1123 1 : TEST_RET_ON_FAIL (test_insert_report_serial (&cls->instances[0]));
1124 : /* Test that a NULL authentication hash/salt is not an error */
1125 1 : TEST_RET_ON_FAIL (test_get_instance_auth_null (&cls->instances[0]));
1126 : /* Test instance private key deletion */
1127 1 : TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[0],
1128 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
1129 1 : TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[1],
1130 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
1131 1 : TEST_RET_ON_FAIL (test_lookup_instances (true,
1132 : 0,
1133 : NULL));
1134 1 : TEST_RET_ON_FAIL (test_lookup_instances (false,
1135 : 1,
1136 : instances));
1137 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[1],
1138 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
1139 1 : TEST_RET_ON_FAIL (test_lookup_instances (false,
1140 : 2,
1141 : instances));
1142 1 : TEST_RET_ON_FAIL (test_lookup_instances (true,
1143 : 1,
1144 : &instances[1]));
1145 1 : TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1],
1146 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
1147 1 : TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1],
1148 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
1149 : /* Test that the instance is gone. */
1150 1 : TEST_RET_ON_FAIL (test_lookup_instances (false,
1151 : 1,
1152 : instances));
1153 1 : return 0;
1154 : }
1155 :
1156 :
1157 : /**
1158 : * Function that tests instances.
1159 : *
1160 : * @return 0 on success, 1 otherwise.
1161 : */
1162 : static int
1163 1 : test_instances (void)
1164 : {
1165 : struct TestInstances_Closure test_cls;
1166 : int test_result;
1167 :
1168 1 : pre_test_instances (&test_cls);
1169 1 : test_result = run_test_instances (&test_cls);
1170 1 : post_test_instances (&test_cls);
1171 1 : return test_result;
1172 : }
1173 :
1174 :
1175 : /* *********** Products ********** */
1176 :
1177 :
1178 : /**
1179 : * A container for data relevant to a product.
1180 : */
1181 : struct ProductData
1182 : {
1183 : /**
1184 : * The identifier of the product.
1185 : */
1186 : const char *id;
1187 :
1188 : /**
1189 : * The details of the product.
1190 : */
1191 : struct TALER_MERCHANTDB_ProductDetails product;
1192 : };
1193 :
1194 :
1195 : /**
1196 : * Creates a product for testing with.
1197 : *
1198 : * @param id the id of the product.
1199 : * @param product the product data to fill.
1200 : */
1201 : static void
1202 6 : make_product (const char *id,
1203 : struct ProductData *product)
1204 : {
1205 : static struct TALER_Amount htwenty40;
1206 :
1207 6 : GNUNET_assert (GNUNET_OK ==
1208 : TALER_string_to_amount ("EUR:120.40",
1209 : &htwenty40));
1210 :
1211 6 : memset (product,
1212 : 0,
1213 : sizeof (*product));
1214 6 : product->id = id;
1215 6 : product->product.product_name = "Test product";
1216 6 : product->product.description = "This is a test product";
1217 6 : product->product.description_i18n = json_array ();
1218 6 : GNUNET_assert (NULL != product->product.description_i18n);
1219 6 : product->product.unit = "boxes";
1220 6 : product->product.minimum_age = 0;
1221 6 : product->product.price_array = &htwenty40;
1222 6 : product->product.price_array_length = 1;
1223 6 : product->product.taxes = json_array ();
1224 6 : GNUNET_assert (NULL != product->product.taxes);
1225 6 : product->product.total_stock = 55;
1226 6 : product->product.total_sold = 0;
1227 6 : product->product.total_lost = 0;
1228 6 : product->product.image = GNUNET_strdup ("");
1229 6 : GNUNET_assert (NULL != product->product.image);
1230 6 : product->product.address = json_array ();
1231 6 : GNUNET_assert (NULL != product->product.address);
1232 6 : product->product.next_restock = GNUNET_TIME_UNIT_ZERO_TS;
1233 6 : }
1234 :
1235 :
1236 : /**
1237 : * Frees memory associated with @e ProductData.
1238 : *
1239 : * @param product the container to free.
1240 : */
1241 : static void
1242 6 : free_product_data (struct ProductData *product)
1243 : {
1244 6 : json_decref (product->product.description_i18n);
1245 6 : json_decref (product->product.taxes);
1246 6 : GNUNET_free (product->product.image);
1247 6 : json_decref (product->product.address);
1248 6 : }
1249 :
1250 :
1251 : /**
1252 : * Compare two products for equality.
1253 : *
1254 : * @param a the first product.
1255 : * @param b the second product.
1256 : * @return 0 on equality, 1 otherwise.
1257 : */
1258 : static int
1259 2 : check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a,
1260 : const struct TALER_MERCHANTDB_ProductDetails *b)
1261 : {
1262 2 : if ((0 != strcmp (a->description,
1263 4 : b->description)) ||
1264 2 : (1 != json_equal (a->description_i18n,
1265 2 : b->description_i18n)) ||
1266 2 : (0 != strcmp (a->unit,
1267 2 : b->unit)) ||
1268 4 : (a->price_array_length != b->price_array_length) ||
1269 2 : (1 != json_equal (a->taxes,
1270 2 : b->taxes)) ||
1271 2 : (a->total_stock != b->total_stock) ||
1272 2 : (a->total_sold != b->total_sold) ||
1273 2 : (a->total_lost != b->total_lost) ||
1274 2 : (0 != strcmp (a->image,
1275 4 : b->image)) ||
1276 2 : (1 != json_equal (a->address,
1277 2 : b->address)) ||
1278 2 : (GNUNET_TIME_timestamp_cmp (a->next_restock,
1279 : !=,
1280 : b->next_restock)))
1281 :
1282 0 : return 1;
1283 4 : for (size_t i = 0; i<a->price_array_length; i++)
1284 2 : if ( (GNUNET_OK !=
1285 2 : TALER_amount_cmp_currency (&a->price_array[i],
1286 4 : &b->price_array[i])) ||
1287 2 : (0 != TALER_amount_cmp (&a->price_array[i],
1288 2 : &b->price_array[i])) )
1289 0 : return 1;
1290 2 : return 0;
1291 : }
1292 :
1293 :
1294 : /**
1295 : * Tests inserting product data into the database.
1296 : *
1297 : * @param instance the instance to insert the product for.
1298 : * @param product the product data to insert.
1299 : * @param num_cats length of the @a cats array
1300 : * @param cats array of categories for the product
1301 : * @param expected_result the result we expect the db to return.
1302 : * @param expect_conflict expected conflict status
1303 : * @param expect_no_instance expected instance missing status
1304 : * @param expected_no_cat expected category missing index
1305 : * @return 0 when successful, 1 otherwise.
1306 : */
1307 : static int
1308 10 : test_insert_product (const struct InstanceData *instance,
1309 : const struct ProductData *product,
1310 : unsigned int num_cats,
1311 : const uint64_t *cats,
1312 : enum GNUNET_DB_QueryStatus expected_result,
1313 : bool expect_conflict,
1314 : bool expect_no_instance,
1315 : ssize_t expected_no_cat)
1316 : {
1317 : bool conflict;
1318 : ssize_t no_cat;
1319 : bool no_group;
1320 : bool no_pot;
1321 :
1322 10 : if (expect_no_instance)
1323 : {
1324 1 : TEST_COND_RET_ON_FAIL (
1325 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
1326 : TALER_MERCHANTDB_set_instance (pg, instance->instance.id),
1327 : "Expected missing instance, but set_instance succeeded\n");
1328 1 : return 0;
1329 : }
1330 9 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
1331 9 : TEST_COND_RET_ON_FAIL (expected_result ==
1332 : TALER_MERCHANTDB_insert_product (pg,
1333 : instance->instance.id,
1334 : product->id,
1335 : &product->product,
1336 : num_cats,
1337 : cats,
1338 : &conflict,
1339 : &no_cat,
1340 : &no_group,
1341 : &no_pot),
1342 : "Insert product failed\n");
1343 9 : if (expected_result > 0)
1344 : {
1345 9 : TEST_COND_RET_ON_FAIL (conflict == expect_conflict,
1346 : "Conflict wrong");
1347 9 : TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat,
1348 : "Wrong category missing returned");
1349 : }
1350 9 : return 0;
1351 : }
1352 :
1353 :
1354 : /**
1355 : * Tests updating product data in the database.
1356 : *
1357 : * @param instance the instance to update the product for.
1358 : * @param product the product data to update.
1359 : * @param expected_result the result we expect the db to return.
1360 : * @return 0 when successful, 1 otherwise.
1361 : */
1362 : static int
1363 7 : test_update_product (const struct InstanceData *instance,
1364 : const struct ProductData *product,
1365 : unsigned int num_cats,
1366 : const uint64_t *cats,
1367 : enum GNUNET_DB_QueryStatus expected_result,
1368 : bool expect_no_instance,
1369 : bool expect_no_product,
1370 : bool expect_lost_reduced,
1371 : bool expect_sold_reduced,
1372 : bool expect_stocked_reduced,
1373 : ssize_t expected_no_cat)
1374 :
1375 : {
1376 : ssize_t no_cat;
1377 : bool no_product;
1378 : bool lost_reduced;
1379 : bool sold_reduced;
1380 : bool stocked_reduced;
1381 : bool no_group;
1382 : bool no_pot;
1383 :
1384 7 : if (expect_no_instance)
1385 : {
1386 0 : TEST_COND_RET_ON_FAIL (
1387 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
1388 : TALER_MERCHANTDB_set_instance (pg, instance->instance.id),
1389 : "Expected missing instance, but set_instance succeeded\n");
1390 0 : return 0;
1391 : }
1392 7 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
1393 7 : TEST_COND_RET_ON_FAIL (
1394 : expected_result ==
1395 : TALER_MERCHANTDB_update_product (pg,
1396 : instance->instance.id,
1397 : product->id,
1398 : &product->product,
1399 : num_cats,
1400 : cats,
1401 : &no_cat,
1402 : &no_product,
1403 : &lost_reduced,
1404 : &sold_reduced,
1405 : &stocked_reduced,
1406 : &no_group,
1407 : &no_pot),
1408 : "Update product failed\n");
1409 7 : if (expected_result > 0)
1410 : {
1411 7 : TEST_COND_RET_ON_FAIL (no_product == expect_no_product,
1412 : "No product wrong");
1413 7 : TEST_COND_RET_ON_FAIL (lost_reduced == expect_lost_reduced,
1414 : "No product wrong");
1415 7 : TEST_COND_RET_ON_FAIL (stocked_reduced == expect_stocked_reduced,
1416 : "Stocked reduced wrong");
1417 7 : TEST_COND_RET_ON_FAIL (sold_reduced == expect_sold_reduced,
1418 : "Sold reduced wrong");
1419 7 : TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat,
1420 : "Wrong category missing returned");
1421 : }
1422 7 : return 0;
1423 : }
1424 :
1425 :
1426 : /**
1427 : * Tests looking up a product from the db.
1428 : *
1429 : * @param instance the instance to query from.
1430 : * @param product the product to query and compare to.
1431 : * @return 0 when successful, 1 otherwise.
1432 : */
1433 : static int
1434 2 : test_lookup_product (const struct InstanceData *instance,
1435 : const struct ProductData *product)
1436 : {
1437 : struct TALER_MERCHANTDB_ProductDetails lookup_result;
1438 2 : size_t num_categories = 0;
1439 2 : uint64_t *categories = NULL;
1440 :
1441 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
1442 2 : if (0 > TALER_MERCHANTDB_get_product (pg,
1443 2 : instance->instance.id,
1444 2 : product->id,
1445 : &lookup_result,
1446 : &num_categories,
1447 : &categories))
1448 : {
1449 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1450 : "Lookup product failed\n");
1451 0 : TALER_MERCHANTDB_product_details_free (&lookup_result);
1452 0 : return 1;
1453 : }
1454 2 : GNUNET_free (categories);
1455 : {
1456 2 : const struct TALER_MERCHANTDB_ProductDetails *to_cmp = &product->product;
1457 :
1458 2 : if (0 != check_products_equal (&lookup_result,
1459 : to_cmp))
1460 : {
1461 0 : GNUNET_break (0);
1462 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1463 : "Lookup product failed: incorrect product returned\n");
1464 0 : TALER_MERCHANTDB_product_details_free (&lookup_result);
1465 0 : return 1;
1466 : }
1467 : }
1468 2 : TALER_MERCHANTDB_product_details_free (&lookup_result);
1469 2 : return 0;
1470 : }
1471 :
1472 :
1473 : /**
1474 : * Closure for testing product lookup
1475 : */
1476 : struct TestLookupProducts_Closure
1477 : {
1478 : /**
1479 : * Number of product ids to compare to
1480 : */
1481 : unsigned int products_to_cmp_length;
1482 :
1483 : /**
1484 : * Pointer to array of product ids
1485 : */
1486 : const struct ProductData *products_to_cmp;
1487 :
1488 : /**
1489 : * Pointer to array of number of matches for each product
1490 : */
1491 : unsigned int *results_matching;
1492 :
1493 : /**
1494 : * Total number of results returned
1495 : */
1496 : unsigned int results_length;
1497 : };
1498 :
1499 :
1500 : /**
1501 : * Function called after calling @e test_lookup_products
1502 : *
1503 : * @param cls a pointer to the lookup closure.
1504 : * @param product_serial DB row ID
1505 : * @param product_id the identifier of the product found.
1506 : */
1507 : static void
1508 4 : lookup_products_cb (void *cls,
1509 : uint64_t product_serial,
1510 : const char *product_id)
1511 : {
1512 4 : struct TestLookupProducts_Closure *cmp = cls;
1513 :
1514 4 : GNUNET_assert (product_serial > 0);
1515 4 : if (NULL == cmp)
1516 0 : return;
1517 4 : cmp->results_length += 1;
1518 10 : for (unsigned int i = 0; cmp->products_to_cmp_length > i; ++i)
1519 : {
1520 6 : if (0 == strcmp (cmp->products_to_cmp[i].id,
1521 : product_id))
1522 4 : cmp->results_matching[i] += 1;
1523 : }
1524 : }
1525 :
1526 :
1527 : /**
1528 : * Tests looking up all products for an instance.
1529 : *
1530 : * @param instance the instance to query from.
1531 : * @param products_length the number of products we are expecting.
1532 : * @param products the list of products that we expect to be found.
1533 : * @return 0 when successful, 1 otherwise.
1534 : */
1535 : static int
1536 4 : test_lookup_products (const struct InstanceData *instance,
1537 : unsigned int products_length,
1538 : const struct ProductData *products)
1539 4 : {
1540 4 : unsigned int results_matching[GNUNET_NZL (products_length)];
1541 4 : struct TestLookupProducts_Closure cls = {
1542 : .products_to_cmp_length = products_length,
1543 : .products_to_cmp = products,
1544 : .results_matching = results_matching,
1545 : .results_length = 0
1546 : };
1547 4 : memset (results_matching, 0, sizeof (unsigned int) * products_length);
1548 4 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
1549 4 : if (0 > TALER_MERCHANTDB_iterate_products (pg,
1550 4 : instance->instance.id,
1551 : 0,
1552 : 20,
1553 : NULL,
1554 : NULL,
1555 : NULL,
1556 : 0,
1557 : &lookup_products_cb,
1558 : &cls))
1559 : {
1560 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1561 : "Lookup products failed\n");
1562 0 : return 1;
1563 : }
1564 4 : if (products_length != cls.results_length)
1565 : {
1566 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1567 : "Lookup products failed: incorrect number of results\n");
1568 0 : return 1;
1569 : }
1570 8 : for (unsigned int i = 0; products_length > i; ++i)
1571 : {
1572 4 : if (1 != cls.results_matching[i])
1573 : {
1574 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1575 : "Lookup products failed: mismatched data\n");
1576 0 : return 1;
1577 : }
1578 : }
1579 4 : return 0;
1580 : }
1581 :
1582 :
1583 : /**
1584 : * Tests deleting a product.
1585 : *
1586 : * @param instance the instance to delete the product from.
1587 : * @param product the product that should be deleted.
1588 : * @param force whether to force deletion of a locked product.
1589 : * @param expect_not_found whether we expect the product to be reported as
1590 : * unknown.
1591 : * @param expect_locked whether we expect deletion to be refused because the
1592 : * product is locked.
1593 : * @return 0 when successful, 1 otherwise.
1594 : */
1595 : static int
1596 5 : test_delete_product (const struct InstanceData *instance,
1597 : const struct ProductData *product,
1598 : bool force,
1599 : bool expect_not_found,
1600 : bool expect_locked)
1601 : {
1602 5 : bool not_found = false;
1603 5 : bool locked = false;
1604 : enum GNUNET_DB_QueryStatus qs;
1605 :
1606 5 : TEST_SET_INSTANCE (instance->instance.id,
1607 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
1608 5 : qs = TALER_MERCHANTDB_delete_product (pg,
1609 5 : instance->instance.id,
1610 5 : product->id,
1611 : force,
1612 : ¬_found,
1613 : &locked);
1614 5 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs,
1615 : "Delete product failed\n");
1616 5 : TEST_COND_RET_ON_FAIL (expect_not_found == not_found,
1617 : "Delete product 'not_found' mismatch\n");
1618 5 : TEST_COND_RET_ON_FAIL (expect_locked == locked,
1619 : "Delete product 'locked' mismatch\n");
1620 5 : return 0;
1621 : }
1622 :
1623 :
1624 : /**
1625 : * Closure for product tests.
1626 : */
1627 : struct TestProducts_Closure
1628 : {
1629 : /**
1630 : * The instance to use for this test.
1631 : */
1632 : struct InstanceData instance;
1633 :
1634 : /**
1635 : * The array of products.
1636 : */
1637 : struct ProductData products[2];
1638 : };
1639 :
1640 :
1641 : /**
1642 : * Sets up the data structures used in the product tests.
1643 : *
1644 : * @param cls the closure to fill with test data.
1645 : */
1646 : static void
1647 1 : pre_test_products (struct TestProducts_Closure *cls)
1648 : {
1649 : static struct TALER_Amount four95;
1650 :
1651 : /* Instance */
1652 1 : make_instance ("test_inst_products",
1653 : &cls->instance);
1654 :
1655 : /* Products */
1656 1 : make_product ("test_products_pd_0",
1657 : &cls->products[0]);
1658 :
1659 1 : make_product ("test_products_pd_1",
1660 : &cls->products[1]);
1661 1 : cls->products[1].product.description = "This is a another test product";
1662 1 : cls->products[1].product.unit = "cans";
1663 1 : cls->products[1].product.minimum_age = 0;
1664 :
1665 1 : GNUNET_assert (GNUNET_OK ==
1666 : TALER_string_to_amount ("EUR:4.95",
1667 : &four95));
1668 1 : cls->products[1].product.price_array = &four95;
1669 1 : cls->products[1].product.price_array_length = 1;
1670 1 : cls->products[1].product.total_stock = 5001;
1671 1 : }
1672 :
1673 :
1674 : /**
1675 : * Handles all teardown after testing.
1676 : *
1677 : * @param cls the closure containing memory to be freed.
1678 : */
1679 : static void
1680 1 : post_test_products (struct TestProducts_Closure *cls)
1681 : {
1682 1 : free_instance_data (&cls->instance);
1683 1 : free_product_data (&cls->products[0]);
1684 1 : free_product_data (&cls->products[1]);
1685 1 : }
1686 :
1687 :
1688 : /**
1689 : * Tests that inserting a category, an OTP device or a webhook that
1690 : * already exists is reported as #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
1691 : * and, crucially, does *not* abort the enclosing transaction.
1692 : *
1693 : * Regression test: these three used to be plain INSERTs without an
1694 : * ON CONFLICT clause. A duplicate key raises 23505, which GNUnet
1695 : * maps to SUCCESS_NO_RESULTS (0); the POST handlers have no branch
1696 : * for 0 and fell through to 'GNUNET_assert (SOFT_ERROR == qs)',
1697 : * aborting taler-merchant-httpd. On top of that the failed INSERT
1698 : * left the transaction in the aborted state, so every following
1699 : * statement failed with 25P02 -- which is what this test checks for,
1700 : * as the query status alone cannot tell the two variants apart.
1701 : *
1702 : * @param instance the instance to run the test against.
1703 : * @return 0 when successful, 1 otherwise.
1704 : */
1705 : static int
1706 1 : test_insert_duplicate_conflicts (const struct InstanceData *instance)
1707 : {
1708 1 : json_t *i18n = json_object ();
1709 1 : struct TALER_MERCHANTDB_OtpDeviceDetails td = {
1710 : .otp_description = (char *) "conflict test device",
1711 : .otp_key = (char *) "my key",
1712 : .otp_algorithm = 1,
1713 : .otp_ctr = 42
1714 : };
1715 1 : struct TALER_MERCHANTDB_WebhookDetails wb = {
1716 : .event_type = (char *) "pay",
1717 : .url = (char *) "http://example.com/",
1718 : .http_method = (char *) "POST",
1719 : .header_template = (char *) "",
1720 : .body_template = (char *) ""
1721 : };
1722 : uint64_t cat;
1723 1 : int ret = 1;
1724 :
1725 1 : GNUNET_assert (NULL != i18n);
1726 1 : TEST_SET_INSTANCE (instance->instance.id,
1727 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
1728 1 : if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1729 1 : TALER_MERCHANTDB_insert_category (pg,
1730 1 : instance->instance.id,
1731 : "duplicate-conflict",
1732 : i18n,
1733 1 : &cat)) ||
1734 : (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1735 1 : TALER_MERCHANTDB_insert_otp_device (pg,
1736 1 : instance->instance.id,
1737 : "duplicate-conflict",
1738 1 : &td)) ||
1739 : (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1740 1 : TALER_MERCHANTDB_insert_webhook (pg,
1741 1 : instance->instance.id,
1742 : "duplicate-conflict",
1743 : &wb)) )
1744 : {
1745 0 : GNUNET_break (0);
1746 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1747 : "Initial insert for the conflict test failed\n");
1748 0 : goto cleanup;
1749 : }
1750 1 : if (GNUNET_OK !=
1751 1 : TALER_MERCHANTDB_start (pg,
1752 : "insert duplicate conflicts"))
1753 : {
1754 0 : GNUNET_break (0);
1755 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1756 : "Failed to start transaction\n");
1757 0 : goto cleanup;
1758 : }
1759 : /* Each of these is a duplicate: expected to be 'no results', and the
1760 : transaction must survive, otherwise the next one fails with a hard
1761 : error (25P02) instead. */
1762 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
1763 1 : TALER_MERCHANTDB_insert_category (pg,
1764 1 : instance->instance.id,
1765 : "duplicate-conflict",
1766 : i18n,
1767 : &cat))
1768 : {
1769 0 : GNUNET_break (0);
1770 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1771 : "Duplicate category insert not reported as 'no results'\n");
1772 0 : goto rollback;
1773 : }
1774 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
1775 1 : TALER_MERCHANTDB_insert_otp_device (pg,
1776 1 : instance->instance.id,
1777 : "duplicate-conflict",
1778 : &td))
1779 : {
1780 0 : GNUNET_break (0);
1781 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1782 : "Duplicate OTP device insert not reported as 'no results'\n");
1783 0 : goto rollback;
1784 : }
1785 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
1786 1 : TALER_MERCHANTDB_insert_webhook (pg,
1787 1 : instance->instance.id,
1788 : "duplicate-conflict",
1789 : &wb))
1790 : {
1791 0 : GNUNET_break (0);
1792 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1793 : "Duplicate webhook insert not reported as 'no results'\n");
1794 0 : goto rollback;
1795 : }
1796 : /* ... and the transaction must still be able to do useful work. */
1797 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1798 1 : TALER_MERCHANTDB_insert_category (pg,
1799 1 : instance->instance.id,
1800 : "duplicate-conflict-other",
1801 : i18n,
1802 : &cat))
1803 : {
1804 0 : GNUNET_break (0);
1805 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1806 : "Insert after duplicate conflict failed\n");
1807 0 : goto rollback;
1808 : }
1809 1 : if (0 >
1810 1 : TALER_MERCHANTDB_commit (pg))
1811 : {
1812 0 : GNUNET_break (0);
1813 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1814 : "Commit after duplicate conflict failed\n");
1815 0 : goto cleanup;
1816 : }
1817 1 : ret = 0;
1818 1 : goto cleanup;
1819 0 : rollback:
1820 0 : TALER_MERCHANTDB_rollback (pg);
1821 1 : cleanup:
1822 1 : json_decref (i18n);
1823 1 : return ret;
1824 : }
1825 :
1826 :
1827 : /**
1828 : * Tests that renaming a category onto the name of an existing
1829 : * category is reported as a conflict (and not as
1830 : * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, which the HTTP layer would
1831 : * turn into a bogus 404 "category unknown"), and that the failed
1832 : * rename does not poison the enclosing transaction.
1833 : *
1834 : * @param instance the instance to run the test against.
1835 : * @return 0 when successful, 1 otherwise.
1836 : */
1837 : static int
1838 1 : test_update_category_conflict (const struct InstanceData *instance)
1839 : {
1840 1 : json_t *i18n = json_object ();
1841 : uint64_t cat1;
1842 : uint64_t cat2;
1843 1 : bool conflict = true;
1844 1 : int ret = 1;
1845 :
1846 1 : GNUNET_assert (NULL != i18n);
1847 1 : TEST_SET_INSTANCE (instance->instance.id,
1848 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
1849 1 : if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1850 1 : TALER_MERCHANTDB_insert_category (pg,
1851 1 : instance->instance.id,
1852 : "category-conflict-a",
1853 : i18n,
1854 1 : &cat1)) ||
1855 : (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1856 1 : TALER_MERCHANTDB_insert_category (pg,
1857 1 : instance->instance.id,
1858 : "category-conflict-b",
1859 : i18n,
1860 : &cat2)) )
1861 : {
1862 0 : GNUNET_break (0);
1863 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1864 : "Insert category failed\n");
1865 0 : goto cleanup;
1866 : }
1867 : /* Renaming 'a' to 'b' must be a conflict, not 'unknown category'. */
1868 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1869 1 : TALER_MERCHANTDB_update_category (pg,
1870 1 : instance->instance.id,
1871 : cat1,
1872 : "category-conflict-b",
1873 : i18n,
1874 : &conflict))
1875 : {
1876 0 : GNUNET_break (0);
1877 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1878 : "Conflicting category rename not reported as conflict\n");
1879 0 : goto cleanup;
1880 : }
1881 1 : if (! conflict)
1882 : {
1883 0 : GNUNET_break (0);
1884 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1885 : "Conflicting category rename did not set 'conflict'\n");
1886 0 : goto cleanup;
1887 : }
1888 : /* ... and the connection must still be usable afterwards. */
1889 1 : conflict = true;
1890 1 : if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1891 1 : TALER_MERCHANTDB_update_category (pg,
1892 1 : instance->instance.id,
1893 : cat1,
1894 : "category-conflict-c",
1895 : i18n,
1896 1 : &conflict)) ||
1897 : conflict)
1898 : {
1899 0 : GNUNET_break (0);
1900 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1901 : "Category rename after conflict failed\n");
1902 0 : goto cleanup;
1903 : }
1904 : /* Unknown category must still be reported as 'no results'. */
1905 1 : conflict = true;
1906 1 : if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
1907 1 : TALER_MERCHANTDB_update_category (pg,
1908 1 : instance->instance.id,
1909 1 : cat1 + cat2 + 424242,
1910 : "category-conflict-d",
1911 : i18n,
1912 1 : &conflict)) ||
1913 : conflict)
1914 : {
1915 0 : GNUNET_break (0);
1916 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1917 : "Rename of unknown category not reported as 'no results'\n");
1918 0 : goto cleanup;
1919 : }
1920 1 : ret = 0;
1921 1 : cleanup:
1922 1 : json_decref (i18n);
1923 1 : return ret;
1924 : }
1925 :
1926 :
1927 : /**
1928 : * Runs the tests for products.
1929 : *
1930 : * @param cls the container of the test data.
1931 : * @return 0 on success, 1 otherwise.
1932 : */
1933 : static int
1934 1 : run_test_products (struct TestProducts_Closure *cls)
1935 : {
1936 : struct GNUNET_Uuid uuid;
1937 : struct GNUNET_TIME_Timestamp refund_deadline =
1938 1 : GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS);
1939 :
1940 : /* Test that insert without an instance fails */
1941 1 : TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
1942 : &cls->products[0],
1943 : 0,
1944 : NULL,
1945 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
1946 : false,
1947 : true,
1948 : -1));
1949 : /* Insert the instance */
1950 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
1951 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
1952 : /* Test that a conflicting category rename is reported as a conflict */
1953 1 : TEST_RET_ON_FAIL (test_update_category_conflict (&cls->instance));
1954 : /* Test that duplicate inserts do not poison the transaction */
1955 1 : TEST_RET_ON_FAIL (test_insert_duplicate_conflicts (&cls->instance));
1956 : /* Test inserting a product */
1957 1 : TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
1958 : &cls->products[0],
1959 : 0,
1960 : NULL,
1961 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
1962 : false,
1963 : false,
1964 : -1));
1965 : /* Test that double insert succeeds */
1966 1 : TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
1967 : &cls->products[0],
1968 : 0,
1969 : NULL,
1970 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
1971 : false,
1972 : false,
1973 : -1));
1974 : /* Test that an insert naming a category that does not exist is
1975 : reported via 'no_cat'.
1976 :
1977 : NOTE: the category existence check in insert_product.sql runs
1978 : *before* any modification of the database (so that a rejected
1979 : request leaves no trace), and therefore also before the
1980 : idempotency/conflict check. An unknown category consequently takes
1981 : precedence over a conflict: 'no_cat' is the 1-based index into the
1982 : supplied category array and 'conflict' stays false. */
1983 : {
1984 1 : uint64_t cat = 42;
1985 :
1986 1 : TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
1987 : &cls->products[0],
1988 : 1,
1989 : &cat,
1990 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
1991 : false,
1992 : false,
1993 : 1));
1994 : }
1995 : /* Test that a genuinely conflicting insert -- same product_id, but
1996 : different product data -- is reported via 'conflict'.
1997 :
1998 : This case supplies no categories at all, so it cannot be masked by
1999 : the category pre-check. The previous version of this test relied on
2000 : a non-existent category to provoke the conflict, which stopped
2001 : exercising the conflict path once that check was hoisted above the
2002 : insert. */
2003 : {
2004 1 : struct ProductData conflicting = cls->products[1];
2005 :
2006 1 : conflicting.id = cls->products[0].id;
2007 1 : TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
2008 : &conflicting,
2009 : 0,
2010 : NULL,
2011 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2012 : true,
2013 : false,
2014 : -1));
2015 : }
2016 : /* Test lookup of individual products */
2017 1 : TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
2018 : &cls->products[0]));
2019 : /* Make sure it fails correctly for products that don't exist */
2020 : {
2021 1 : size_t num_categories = 0;
2022 1 : uint64_t *categories = NULL;
2023 :
2024 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
2025 1 : TALER_MERCHANTDB_get_product (pg,
2026 1 : cls->instance.instance.id,
2027 : "nonexistent_product",
2028 : NULL,
2029 : &num_categories,
2030 : &categories))
2031 : {
2032 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2033 : "Lookup product failed\n");
2034 0 : return 1;
2035 : }
2036 1 : GNUNET_free (categories);
2037 : }
2038 : /* Test product update */
2039 1 : cls->products[0].product.description =
2040 : "This is a test product that has been updated!";
2041 1 : GNUNET_assert (0 ==
2042 : json_array_append_new (
2043 : cls->products[0].product.description_i18n,
2044 : json_string (
2045 : "description in another language")));
2046 1 : cls->products[0].product.unit = "barrels";
2047 : {
2048 : static struct TALER_Amount seven68;
2049 :
2050 1 : GNUNET_assert (GNUNET_OK ==
2051 : TALER_string_to_amount ("EUR:7.68",
2052 : &seven68));
2053 1 : cls->products[0].product.price_array = &seven68;
2054 1 : cls->products[0].product.price_array_length = 1;
2055 : }
2056 1 : GNUNET_assert (0 ==
2057 : json_array_append_new (cls->products[0].product.taxes,
2058 : json_string ("2% sales tax")));
2059 1 : cls->products[0].product.total_stock = 100;
2060 1 : cls->products[0].product.total_sold = 0; /* will be ignored! */
2061 1 : cls->products[0].product.total_lost = 7;
2062 1 : GNUNET_free (cls->products[0].product.image);
2063 1 : cls->products[0].product.image = GNUNET_strdup ("image");
2064 1 : GNUNET_assert (0 ==
2065 : json_array_append_new (cls->products[0].product.address,
2066 : json_string ("444 Some Street")));
2067 1 : cls->products[0].product.next_restock = GNUNET_TIME_timestamp_get ();
2068 1 : TEST_RET_ON_FAIL (test_update_product (
2069 : &cls->instance,
2070 : &cls->products[0],
2071 : 0,
2072 : NULL,
2073 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2074 : false,
2075 : false,
2076 : false,
2077 : false,
2078 : false,
2079 : -1));
2080 :
2081 : /* Stock is the pair (total_stock, total_stock_frac): raise the
2082 : fractional part, then check that lowering only the fractional part
2083 : is refused just like lowering the integer part. */
2084 1 : cls->products[0].product.total_stock_frac = 500000;
2085 1 : TEST_RET_ON_FAIL (test_update_product (
2086 : &cls->instance,
2087 : &cls->products[0],
2088 : 0,
2089 : NULL,
2090 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2091 : false,
2092 : false,
2093 : false,
2094 : false,
2095 : false,
2096 : -1));
2097 : {
2098 1 : struct ProductData frac_dec = cls->products[0];
2099 :
2100 1 : frac_dec.product.total_stock_frac = 400000;
2101 1 : TEST_RET_ON_FAIL (test_update_product (
2102 : &cls->instance,
2103 : &frac_dec,
2104 : 0,
2105 : NULL,
2106 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2107 : false,
2108 : false,
2109 : false,
2110 : false,
2111 : true,
2112 : -1));
2113 : }
2114 : {
2115 1 : struct ProductData stock_dec = cls->products[0];
2116 :
2117 1 : stock_dec.product.total_stock = 40;
2118 1 : TEST_RET_ON_FAIL (test_update_product (
2119 : &cls->instance,
2120 : &stock_dec,
2121 : 0,
2122 : NULL,
2123 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2124 : false,
2125 : false,
2126 : false,
2127 : false,
2128 : true,
2129 : -1));
2130 : }
2131 : {
2132 1 : struct ProductData lost_dec = cls->products[0];
2133 :
2134 1 : lost_dec.product.total_lost = 1;
2135 1 : TEST_RET_ON_FAIL (test_update_product (
2136 : &cls->instance,
2137 : &lost_dec,
2138 : 0,
2139 : NULL,
2140 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2141 : false,
2142 : false,
2143 : true,
2144 : false,
2145 : false,
2146 : -1));
2147 : }
2148 1 : TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
2149 : &cls->products[0]));
2150 1 : TEST_RET_ON_FAIL (test_update_product (
2151 : &cls->instance,
2152 : &cls->products[1],
2153 : 0,
2154 : NULL,
2155 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2156 : false,
2157 : true,
2158 : false,
2159 : false,
2160 : false,
2161 : -1));
2162 : /* Test collective product lookup */
2163 1 : TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
2164 : &cls->products[1],
2165 : 0,
2166 : NULL,
2167 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2168 : false,
2169 : false,
2170 : -1));
2171 1 : TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
2172 : 2,
2173 : cls->products));
2174 : /* Test locking */
2175 1 : uuid.value[0] = 0x1287346a;
2176 1 : if (0 != TALER_MERCHANTDB_insert_inventory_lock (pg,
2177 1 : cls->instance.instance.id,
2178 : cls->products[0].id,
2179 : &uuid,
2180 : 256,
2181 : 0,
2182 : refund_deadline))
2183 : {
2184 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2185 : "Lock product failed\n");
2186 0 : return 1;
2187 : }
2188 1 : if (1 != TALER_MERCHANTDB_insert_inventory_lock (pg,
2189 1 : cls->instance.instance.id,
2190 : cls->products[0].id,
2191 : &uuid,
2192 : 1,
2193 : 0,
2194 : refund_deadline))
2195 : {
2196 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2197 : "Lock product failed\n");
2198 0 : return 1;
2199 : }
2200 : /* Test product deletion of an unlocked product */
2201 1 : TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
2202 : &cls->products[1],
2203 : false,
2204 : false,
2205 : false));
2206 : /* Test double deletion reports 'not found' */
2207 1 : TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
2208 : &cls->products[1],
2209 : false,
2210 : true,
2211 : false));
2212 1 : TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
2213 : 1,
2214 : cls->products));
2215 : /* Test unlocking */
2216 1 : if (1 != TALER_MERCHANTDB_delete_inventory_lock (pg,
2217 : &uuid))
2218 : {
2219 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2220 : "Unlock inventory failed\n");
2221 0 : return 1;
2222 : }
2223 1 : if (0 != TALER_MERCHANTDB_delete_inventory_lock (pg,
2224 : &uuid))
2225 : {
2226 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2227 : "Unlock inventory failed\n");
2228 0 : return 1;
2229 : }
2230 : /* Re-lock products[0] to exercise deletion of a locked product */
2231 1 : if (1 != TALER_MERCHANTDB_insert_inventory_lock (pg,
2232 1 : cls->instance.instance.id,
2233 : cls->products[0].id,
2234 : &uuid,
2235 : 1,
2236 : 0,
2237 : refund_deadline))
2238 : {
2239 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2240 : "Lock product failed\n");
2241 0 : return 1;
2242 : }
2243 : /* Deletion without force must be refused while the product is locked */
2244 1 : TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
2245 : &cls->products[0],
2246 : false,
2247 : false,
2248 : true));
2249 1 : TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
2250 : 1,
2251 : cls->products));
2252 : /* Forced deletion releases the lock and removes the product */
2253 1 : TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
2254 : &cls->products[0],
2255 : true,
2256 : false,
2257 : false));
2258 1 : TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
2259 : 0,
2260 : NULL));
2261 : /* Updating a product that has just been deleted must be reported via
2262 : 'no_product' (which the HTTP layer turns into a 404), never as a
2263 : hard error. merchant_do_update_product re-reads the inventory row
2264 : and then ASSERTs that the subsequent UPDATE still finds it; without
2265 : the FOR UPDATE on that re-read, a DELETE committing in the window
2266 : between the two statements turns this into a P0004 assertion
2267 : failure (HTTP 500). Reproducing that window needs a second
2268 : database connection, which this harness does not have; what is
2269 : asserted here is that the very same code path returns 'no_product'
2270 : when the row is gone. */
2271 1 : TEST_RET_ON_FAIL (test_update_product (
2272 : &cls->instance,
2273 : &cls->products[0],
2274 : 0,
2275 : NULL,
2276 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2277 : false,
2278 : true,
2279 : false,
2280 : false,
2281 : false,
2282 : -1));
2283 1 : return 0;
2284 : }
2285 :
2286 :
2287 : /**
2288 : * Takes care of product testing.
2289 : *
2290 : * @return 0 on success, 1 otherwise.
2291 : */
2292 : static int
2293 1 : test_products (void)
2294 : {
2295 : struct TestProducts_Closure test_cls;
2296 : int test_result;
2297 :
2298 1 : pre_test_products (&test_cls);
2299 1 : test_result = run_test_products (&test_cls);
2300 1 : post_test_products (&test_cls);
2301 1 : return test_result;
2302 : }
2303 :
2304 :
2305 : /* ********** Inventory locks ********** */
2306 :
2307 :
2308 : /**
2309 : * Container for the data used by the inventory lock tests.
2310 : */
2311 : struct TestLocks_Closure
2312 : {
2313 : /**
2314 : * The instance to use for this test.
2315 : */
2316 : struct InstanceData instance;
2317 :
2318 : /**
2319 : * The products we lock; [0] has a limited stock, [1] carries the
2320 : * INT64_MAX "unlimited stock" sentinel.
2321 : */
2322 : struct ProductData products[2];
2323 : };
2324 :
2325 :
2326 : /**
2327 : * Checks that @a product has exactly @a expected_locked units locked.
2328 : *
2329 : * @param instance the instance owning the product.
2330 : * @param product the product to inspect.
2331 : * @param expected_locked expected value of total_locked.
2332 : * @param expected_locked_frac expected value of total_locked_frac.
2333 : * @return 0 when successful, 1 otherwise.
2334 : */
2335 : static int
2336 9 : test_product_locked (const struct InstanceData *instance,
2337 : const struct ProductData *product,
2338 : uint64_t expected_locked,
2339 : uint32_t expected_locked_frac)
2340 : {
2341 : struct TALER_MERCHANTDB_ProductDetails pd;
2342 9 : size_t num_categories = 0;
2343 9 : uint64_t *categories = NULL;
2344 9 : int ret = 0;
2345 :
2346 9 : memset (&pd,
2347 : 0,
2348 : sizeof (pd));
2349 9 : TEST_SET_INSTANCE (instance->instance.id,
2350 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
2351 9 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
2352 : TALER_MERCHANTDB_get_product (pg,
2353 : instance->instance.id,
2354 : product->id,
2355 : &pd,
2356 : &num_categories,
2357 : &categories),
2358 : "Lookup of locked product failed\n");
2359 9 : GNUNET_free (categories);
2360 9 : if ( (expected_locked != pd.total_locked) ||
2361 9 : (expected_locked_frac != pd.total_locked_frac) )
2362 : {
2363 0 : GNUNET_break (0);
2364 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2365 : "Product `%s' has total_locked=%llu.%06u,"
2366 : " expected %llu.%06u\n",
2367 : product->id,
2368 : (unsigned long long) pd.total_locked,
2369 : (unsigned int) pd.total_locked_frac,
2370 : (unsigned long long) expected_locked,
2371 : (unsigned int) expected_locked_frac);
2372 0 : ret = 1;
2373 : }
2374 9 : TALER_MERCHANTDB_product_details_free (&pd);
2375 9 : return ret;
2376 : }
2377 :
2378 :
2379 : /**
2380 : * Tests locking @a quantity units of @a product under @a uuid.
2381 : *
2382 : * @param instance the instance owning the product.
2383 : * @param product the product to lock.
2384 : * @param uuid identifier of the lock.
2385 : * @param quantity how many units to lock.
2386 : * @param expected_result the result we expect the db to return.
2387 : * @return 0 when successful, 1 otherwise.
2388 : */
2389 : static int
2390 9 : test_insert_inventory_lock (const struct InstanceData *instance,
2391 : const struct ProductData *product,
2392 : const struct GNUNET_Uuid *uuid,
2393 : uint64_t quantity,
2394 : enum GNUNET_DB_QueryStatus expected_result)
2395 : {
2396 : struct GNUNET_TIME_Timestamp expiration
2397 9 : = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS);
2398 :
2399 9 : TEST_SET_INSTANCE (instance->instance.id,
2400 : expected_result);
2401 9 : TEST_COND_RET_ON_FAIL (expected_result ==
2402 : TALER_MERCHANTDB_insert_inventory_lock (
2403 : pg,
2404 : instance->instance.id,
2405 : product->id,
2406 : uuid,
2407 : quantity,
2408 : 0,
2409 : expiration),
2410 : "Insert inventory lock returned unexpected status\n");
2411 9 : return 0;
2412 : }
2413 :
2414 :
2415 : /**
2416 : * Sets up the data structures used in the inventory lock tests.
2417 : *
2418 : * @param cls the closure to fill with test data.
2419 : */
2420 : static void
2421 1 : pre_test_locks (struct TestLocks_Closure *cls)
2422 : {
2423 1 : make_instance ("test_inst_locks",
2424 : &cls->instance);
2425 1 : make_product ("test_locks_pd_0",
2426 : &cls->products[0]);
2427 1 : cls->products[0].product.total_stock = 100;
2428 1 : make_product ("test_locks_pd_1",
2429 : &cls->products[1]);
2430 1 : cls->products[1].product.total_stock = INT64_MAX;
2431 1 : }
2432 :
2433 :
2434 : /**
2435 : * Handles all teardown after testing.
2436 : *
2437 : * @param cls the closure containing memory to be freed.
2438 : */
2439 : static void
2440 1 : post_test_locks (struct TestLocks_Closure *cls)
2441 : {
2442 1 : free_instance_data (&cls->instance);
2443 1 : free_product_data (&cls->products[0]);
2444 1 : free_product_data (&cls->products[1]);
2445 1 : }
2446 :
2447 :
2448 : /**
2449 : * Runs the tests for inventory locks.
2450 : *
2451 : * @param cls the container of the test data.
2452 : * @return 0 on success, 1 otherwise.
2453 : */
2454 : static int
2455 1 : run_test_locks (struct TestLocks_Closure *cls)
2456 : {
2457 : struct GNUNET_Uuid uuid1;
2458 : struct GNUNET_Uuid uuid2;
2459 :
2460 1 : memset (&uuid1,
2461 : 0,
2462 : sizeof (uuid1));
2463 1 : memset (&uuid2,
2464 : 0,
2465 : sizeof (uuid2));
2466 1 : uuid1.value[0] = 0x11111111;
2467 1 : uuid2.value[0] = 0x22222222;
2468 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
2469 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
2470 1 : TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
2471 : &cls->products[0],
2472 : 0,
2473 : NULL,
2474 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2475 : false,
2476 : false,
2477 : -1));
2478 : /* Lock 40 of the 100 units in stock */
2479 1 : TEST_RET_ON_FAIL (test_insert_inventory_lock (
2480 : &cls->instance,
2481 : &cls->products[0],
2482 : &uuid1,
2483 : 40,
2484 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
2485 1 : TEST_RET_ON_FAIL (test_product_locked (&cls->instance,
2486 : &cls->products[0],
2487 : 40,
2488 : 0));
2489 : /* Re-posting a lock for the same UUID *updates* the existing lock,
2490 : the locks must not stack up. */
2491 1 : TEST_RET_ON_FAIL (test_insert_inventory_lock (
2492 : &cls->instance,
2493 : &cls->products[0],
2494 : &uuid1,
2495 : 10,
2496 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
2497 1 : TEST_RET_ON_FAIL (test_product_locked (&cls->instance,
2498 : &cls->products[0],
2499 : 10,
2500 : 0));
2501 : /* A second lock competes with the first one for the remaining stock */
2502 1 : TEST_RET_ON_FAIL (test_insert_inventory_lock (
2503 : &cls->instance,
2504 : &cls->products[0],
2505 : &uuid2,
2506 : 91,
2507 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
2508 1 : TEST_RET_ON_FAIL (test_product_locked (&cls->instance,
2509 : &cls->products[0],
2510 : 10,
2511 : 0));
2512 1 : TEST_RET_ON_FAIL (test_insert_inventory_lock (
2513 : &cls->instance,
2514 : &cls->products[0],
2515 : &uuid2,
2516 : 90,
2517 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
2518 1 : TEST_RET_ON_FAIL (test_product_locked (&cls->instance,
2519 : &cls->products[0],
2520 : 100,
2521 : 0));
2522 : /* Locking a quantity of zero releases the lock */
2523 1 : TEST_RET_ON_FAIL (test_insert_inventory_lock (
2524 : &cls->instance,
2525 : &cls->products[0],
2526 : &uuid1,
2527 : 0,
2528 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
2529 1 : TEST_RET_ON_FAIL (test_product_locked (&cls->instance,
2530 : &cls->products[0],
2531 : 90,
2532 : 0));
2533 : /* Unlocking an UUID that holds no lock is a no-op, not an error */
2534 1 : TEST_RET_ON_FAIL (test_insert_inventory_lock (
2535 : &cls->instance,
2536 : &cls->products[0],
2537 : &uuid1,
2538 : 0,
2539 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
2540 1 : TEST_RET_ON_FAIL (test_product_locked (&cls->instance,
2541 : &cls->products[0],
2542 : 90,
2543 : 0));
2544 : /* Explicitly dropping the remaining lock frees the stock again */
2545 1 : TEST_SET_INSTANCE (cls->instance.instance.id,
2546 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
2547 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
2548 : TALER_MERCHANTDB_delete_inventory_lock (pg,
2549 : &uuid2),
2550 : "Unlock inventory failed\n");
2551 1 : TEST_RET_ON_FAIL (test_product_locked (&cls->instance,
2552 : &cls->products[0],
2553 : 0,
2554 : 0));
2555 : /* Locking an unknown product is reported as 'no results' */
2556 : {
2557 1 : struct ProductData unknown = cls->products[0];
2558 :
2559 1 : unknown.id = "test_locks_pd_unknown";
2560 1 : TEST_RET_ON_FAIL (test_insert_inventory_lock (
2561 : &cls->instance,
2562 : &unknown,
2563 : &uuid1,
2564 : 1,
2565 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
2566 : }
2567 : /* Products with the INT64_MAX "unlimited stock" sentinel skip the
2568 : availability check, so two large locks can exceed what the
2569 : total_locked counter can represent. That must saturate, not blow
2570 : up with 'bigint out of range' (which the caller turns into a 500). */
2571 1 : TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
2572 : &cls->products[1],
2573 : 0,
2574 : NULL,
2575 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2576 : false,
2577 : false,
2578 : -1));
2579 1 : TEST_RET_ON_FAIL (test_insert_inventory_lock (
2580 : &cls->instance,
2581 : &cls->products[1],
2582 : &uuid1,
2583 : INT64_MAX,
2584 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
2585 1 : TEST_RET_ON_FAIL (test_product_locked (&cls->instance,
2586 : &cls->products[1],
2587 : INT64_MAX,
2588 : 0));
2589 1 : TEST_RET_ON_FAIL (test_insert_inventory_lock (
2590 : &cls->instance,
2591 : &cls->products[1],
2592 : &uuid2,
2593 : INT64_MAX,
2594 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
2595 1 : TEST_RET_ON_FAIL (test_product_locked (&cls->instance,
2596 : &cls->products[1],
2597 : INT64_MAX,
2598 : 999999));
2599 1 : return 0;
2600 : }
2601 :
2602 :
2603 : /**
2604 : * Takes care of inventory lock testing.
2605 : *
2606 : * @return 0 on success, 1 otherwise.
2607 : */
2608 : static int
2609 1 : test_inventory_locks (void)
2610 : {
2611 : struct TestLocks_Closure test_cls;
2612 : int test_result;
2613 :
2614 1 : pre_test_locks (&test_cls);
2615 1 : test_result = run_test_locks (&test_cls);
2616 1 : post_test_locks (&test_cls);
2617 1 : return test_result;
2618 : }
2619 :
2620 :
2621 : /* ********** Tokens ********** */
2622 :
2623 :
2624 : /**
2625 : * Container for the data used by the token tests.
2626 : */
2627 : struct TestTokens_Closure
2628 : {
2629 : /**
2630 : * The instance to use for this test.
2631 : */
2632 : struct InstanceData instance;
2633 :
2634 : /**
2635 : * Details of the token family we operate on.
2636 : */
2637 : struct TALER_MERCHANTDB_TokenFamilyDetails family;
2638 :
2639 : /**
2640 : * Public key of the token family key.
2641 : */
2642 : struct TALER_TokenIssuePublicKey pub;
2643 :
2644 : /**
2645 : * Private key of the token family key.
2646 : */
2647 : struct TALER_TokenIssuePrivateKey priv;
2648 :
2649 : /**
2650 : * Hash of @e pub, identifies the token family key in the DB.
2651 : */
2652 : struct TALER_TokenIssuePublicKeyHashP h_pub;
2653 :
2654 : /**
2655 : * Hash of the contract the tokens are issued for.
2656 : */
2657 : struct TALER_PrivateContractHashP h_contract_terms;
2658 : };
2659 :
2660 :
2661 : /**
2662 : * Fabricate a blinded token issue signature. Its value is never
2663 : * inspected by the database, so a random CS answer is good enough; we
2664 : * only care that identical inputs yield identical bytes.
2665 : *
2666 : * @param[out] bs storage for the blinded signature
2667 : * @param[out] sig set to point to @a bs
2668 : */
2669 : static void
2670 2 : make_blinded_token_sig (struct GNUNET_CRYPTO_BlindedSignature *bs,
2671 : struct TALER_BlindedTokenIssueSignature *sig)
2672 : {
2673 2 : memset (bs,
2674 : 0,
2675 : sizeof (*bs));
2676 2 : bs->cipher = GNUNET_CRYPTO_BSA_CS;
2677 2 : bs->rc = 1;
2678 2 : GNUNET_CRYPTO_random_block (&bs->details.blinded_cs_answer,
2679 : sizeof (bs->details.blinded_cs_answer));
2680 2 : sig->signature = bs;
2681 2 : }
2682 :
2683 :
2684 : /**
2685 : * Fabricate an (unblinded) token issue signature.
2686 : *
2687 : * @param[out] ub storage for the unblinded signature
2688 : * @param[out] sig set to point to @a ub
2689 : */
2690 : static void
2691 1 : make_token_issue_sig (struct GNUNET_CRYPTO_UnblindedSignature *ub,
2692 : struct TALER_TokenIssueSignature *sig)
2693 : {
2694 1 : memset (ub,
2695 : 0,
2696 : sizeof (*ub));
2697 1 : ub->cipher = GNUNET_CRYPTO_BSA_CS;
2698 1 : ub->rc = 1;
2699 1 : GNUNET_CRYPTO_random_block (&ub->details.cs_signature,
2700 : sizeof (ub->details.cs_signature));
2701 1 : sig->signature = ub;
2702 1 : }
2703 :
2704 :
2705 : /**
2706 : * Checks the issuance/spending counters of the token family.
2707 : *
2708 : * @param cls the test data.
2709 : * @param expected_issued number of issued tokens we expect.
2710 : * @param expected_used number of spent tokens we expect.
2711 : * @return 0 when successful, 1 otherwise.
2712 : */
2713 : static int
2714 7 : test_token_family_counters (const struct TestTokens_Closure *cls,
2715 : uint64_t expected_issued,
2716 : uint64_t expected_used)
2717 : {
2718 : struct TALER_MERCHANTDB_TokenFamilyDetails details;
2719 7 : int ret = 0;
2720 :
2721 7 : memset (&details,
2722 : 0,
2723 : sizeof (details));
2724 7 : TEST_SET_INSTANCE (cls->instance.instance.id,
2725 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
2726 7 : TEST_COND_RET_ON_FAIL (
2727 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
2728 : TALER_MERCHANTDB_get_token_family (pg,
2729 : cls->instance.instance.id,
2730 : cls->family.slug,
2731 : &details),
2732 : "Lookup of token family failed\n");
2733 7 : if ( (expected_issued != details.issued) ||
2734 7 : (expected_used != details.used) )
2735 : {
2736 0 : GNUNET_break (0);
2737 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2738 : "Token family counters are issued=%llu/used=%llu,"
2739 : " expected issued=%llu/used=%llu\n",
2740 : (unsigned long long) details.issued,
2741 : (unsigned long long) details.used,
2742 : (unsigned long long) expected_issued,
2743 : (unsigned long long) expected_used);
2744 0 : ret = 1;
2745 : }
2746 7 : TALER_MERCHANTDB_token_family_details_free (&details);
2747 7 : return ret;
2748 : }
2749 :
2750 :
2751 : /**
2752 : * Tests inserting an issued token.
2753 : *
2754 : * @param cls the test data.
2755 : * @param blind_sig the blinded signature of the issued token.
2756 : * @param expected_result the result we expect the db to return.
2757 : * @param expect_no_family whether we expect the token family key to be
2758 : * reported as unknown.
2759 : * @return 0 when successful, 1 otherwise.
2760 : */
2761 : static int
2762 4 : test_insert_issued_token (const struct TestTokens_Closure *cls,
2763 : const struct TALER_BlindedTokenIssueSignature *
2764 : blind_sig,
2765 : enum GNUNET_DB_QueryStatus expected_result,
2766 : bool expect_no_family)
2767 : {
2768 : /* Deliberately poisoned: the DB layer must assign the flag on every
2769 : path, including the ones where it returns early. Note that the
2770 : 'qs <= 0' early return itself is not reachable from here: a stored
2771 : procedure with OUT parameters always yields exactly one row. */
2772 4 : bool no_family = true;
2773 :
2774 4 : TEST_SET_INSTANCE (cls->instance.instance.id,
2775 : expected_result);
2776 4 : TEST_COND_RET_ON_FAIL (
2777 : expected_result ==
2778 : TALER_MERCHANTDB_insert_issued_token (pg,
2779 : &cls->h_contract_terms,
2780 : &cls->h_pub,
2781 : blind_sig,
2782 : &no_family),
2783 : "Insert issued token returned unexpected status\n");
2784 4 : TEST_COND_RET_ON_FAIL (expect_no_family == no_family,
2785 : "Insert issued token 'no_family' mismatch\n");
2786 4 : return 0;
2787 : }
2788 :
2789 :
2790 : /**
2791 : * Tests inserting a spent token.
2792 : *
2793 : * @param cls the test data.
2794 : * @param use_pub public key of the token being spent.
2795 : * @param use_sig signature made with the token use key.
2796 : * @param issue_sig signature of the merchant over the token.
2797 : * @param expected_result the result we expect the db to return.
2798 : * @param expect_no_family whether we expect the token family key to be
2799 : * reported as unknown.
2800 : * @return 0 when successful, 1 otherwise.
2801 : */
2802 : static int
2803 4 : test_insert_used_token (const struct TestTokens_Closure *cls,
2804 : const struct TALER_TokenUsePublicKeyP *use_pub,
2805 : const struct TALER_TokenUseSignatureP *use_sig,
2806 : const struct TALER_TokenIssueSignature *issue_sig,
2807 : enum GNUNET_DB_QueryStatus expected_result,
2808 : bool expect_no_family)
2809 : {
2810 : /* Deliberately poisoned: see test_insert_issued_token(). */
2811 4 : bool no_family = true;
2812 :
2813 4 : TEST_SET_INSTANCE (cls->instance.instance.id,
2814 : expected_result);
2815 4 : TEST_COND_RET_ON_FAIL (
2816 : expected_result ==
2817 : TALER_MERCHANTDB_insert_used_token (pg,
2818 : &cls->h_contract_terms,
2819 : &cls->h_pub,
2820 : use_pub,
2821 : use_sig,
2822 : issue_sig,
2823 : &no_family),
2824 : "Insert used token returned unexpected status\n");
2825 4 : TEST_COND_RET_ON_FAIL (expect_no_family == no_family,
2826 : "Insert used token 'no_family' mismatch\n");
2827 4 : return 0;
2828 : }
2829 :
2830 :
2831 : /**
2832 : * Sets up the data structures used in the token tests.
2833 : *
2834 : * @param cls the closure to fill with test data.
2835 : */
2836 : static void
2837 1 : pre_test_tokens (struct TestTokens_Closure *cls)
2838 : {
2839 1 : make_instance ("test_inst_tokens",
2840 : &cls->instance);
2841 1 : memset (&cls->family,
2842 : 0,
2843 : sizeof (cls->family));
2844 1 : cls->family.slug = (char *) "test_tokens_family";
2845 1 : cls->family.name = (char *) "Test token family";
2846 1 : cls->family.description = (char *) "This is a test token family";
2847 1 : cls->family.description_i18n = json_object ();
2848 1 : GNUNET_assert (NULL != cls->family.description_i18n);
2849 1 : cls->family.valid_after = GNUNET_TIME_timestamp_get ();
2850 : cls->family.valid_before
2851 1 : = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_YEARS);
2852 1 : cls->family.duration = GNUNET_TIME_UNIT_DAYS;
2853 1 : cls->family.validity_granularity = GNUNET_TIME_UNIT_DAYS;
2854 1 : cls->family.start_offset = GNUNET_TIME_UNIT_ZERO;
2855 1 : cls->family.kind = TALER_MERCHANTDB_TFK_Discount;
2856 1 : GNUNET_CRYPTO_blind_sign_keys_create (&cls->priv.private_key,
2857 : &cls->pub.public_key,
2858 : GNUNET_CRYPTO_BSA_CS);
2859 1 : cls->h_pub.hash = cls->pub.public_key->pub_key_hash;
2860 1 : GNUNET_CRYPTO_random_block (&cls->h_contract_terms,
2861 : sizeof (cls->h_contract_terms));
2862 1 : }
2863 :
2864 :
2865 : /**
2866 : * Handles all teardown after testing.
2867 : *
2868 : * @param cls the closure containing memory to be freed.
2869 : */
2870 : static void
2871 1 : post_test_tokens (struct TestTokens_Closure *cls)
2872 : {
2873 1 : GNUNET_CRYPTO_blind_sign_pub_decref (cls->pub.public_key);
2874 1 : GNUNET_CRYPTO_blind_sign_priv_decref (cls->priv.private_key);
2875 1 : json_decref (cls->family.description_i18n);
2876 1 : free_instance_data (&cls->instance);
2877 1 : }
2878 :
2879 :
2880 : /**
2881 : * Runs the tests for issued and spent tokens.
2882 : *
2883 : * @param cls the container of the test data.
2884 : * @return 0 on success, 1 otherwise.
2885 : */
2886 : static int
2887 1 : run_test_tokens (struct TestTokens_Closure *cls)
2888 : {
2889 : struct GNUNET_CRYPTO_BlindedSignature bs1;
2890 : struct GNUNET_CRYPTO_BlindedSignature bs2;
2891 : struct TALER_BlindedTokenIssueSignature blind_sig1;
2892 : struct TALER_BlindedTokenIssueSignature blind_sig2;
2893 : struct GNUNET_CRYPTO_UnblindedSignature ub1;
2894 : struct TALER_TokenIssueSignature issue_sig;
2895 : struct TALER_TokenUsePublicKeyP use_pub;
2896 : struct TALER_TokenUseSignatureP use_sig;
2897 : struct TALER_TokenUseSignatureP other_sig;
2898 :
2899 1 : make_blinded_token_sig (&bs1,
2900 : &blind_sig1);
2901 1 : make_blinded_token_sig (&bs2,
2902 : &blind_sig2);
2903 1 : make_token_issue_sig (&ub1,
2904 : &issue_sig);
2905 1 : GNUNET_CRYPTO_random_block (&use_pub,
2906 : sizeof (use_pub));
2907 1 : GNUNET_CRYPTO_random_block (&use_sig,
2908 : sizeof (use_sig));
2909 1 : GNUNET_CRYPTO_random_block (&other_sig,
2910 : sizeof (other_sig));
2911 : /* Set up instance, token family and token family key */
2912 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
2913 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
2914 1 : TEST_SET_INSTANCE (cls->instance.instance.id,
2915 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
2916 1 : TEST_COND_RET_ON_FAIL (
2917 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
2918 : TALER_MERCHANTDB_insert_token_family (pg,
2919 : cls->instance.instance.id,
2920 : cls->family.slug,
2921 : &cls->family),
2922 : "Insert token family failed\n");
2923 1 : TEST_SET_INSTANCE (cls->instance.instance.id,
2924 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
2925 1 : TEST_COND_RET_ON_FAIL (
2926 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
2927 : TALER_MERCHANTDB_insert_token_family_key (pg,
2928 : cls->instance.instance.id,
2929 : cls->family.slug,
2930 : &cls->pub,
2931 : &cls->priv,
2932 : cls->family.valid_before,
2933 : cls->family.valid_after,
2934 : cls->family.valid_before),
2935 : "Insert token family key failed\n");
2936 1 : TEST_RET_ON_FAIL (test_token_family_counters (cls,
2937 : 0,
2938 : 0));
2939 : /* Issuing a token bumps the 'issued' counter */
2940 1 : TEST_RET_ON_FAIL (test_insert_issued_token (
2941 : cls,
2942 : &blind_sig1,
2943 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2944 : false));
2945 1 : TEST_RET_ON_FAIL (test_token_family_counters (cls,
2946 : 1,
2947 : 0));
2948 : /* Re-issuing the very same token must be detected as a duplicate
2949 : and must NOT bump the counter a second time. */
2950 1 : TEST_RET_ON_FAIL (test_insert_issued_token (
2951 : cls,
2952 : &blind_sig1,
2953 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS,
2954 : false));
2955 1 : TEST_RET_ON_FAIL (test_token_family_counters (cls,
2956 : 1,
2957 : 0));
2958 : /* A different blind signature is a different token */
2959 1 : TEST_RET_ON_FAIL (test_insert_issued_token (
2960 : cls,
2961 : &blind_sig2,
2962 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2963 : false));
2964 1 : TEST_RET_ON_FAIL (test_token_family_counters (cls,
2965 : 2,
2966 : 0));
2967 : /* Spending a token bumps the 'used' counter */
2968 1 : TEST_RET_ON_FAIL (test_insert_used_token (
2969 : cls,
2970 : &use_pub,
2971 : &use_sig,
2972 : &issue_sig,
2973 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2974 : false));
2975 1 : TEST_RET_ON_FAIL (test_token_family_counters (cls,
2976 : 2,
2977 : 1));
2978 : /* Replaying the exact same spend is idempotent: reported as success,
2979 : but the 'used' counter must not move a second time. */
2980 1 : TEST_RET_ON_FAIL (test_insert_used_token (
2981 : cls,
2982 : &use_pub,
2983 : &use_sig,
2984 : &issue_sig,
2985 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
2986 : false));
2987 1 : TEST_RET_ON_FAIL (test_token_family_counters (cls,
2988 : 2,
2989 : 1));
2990 : /* Reusing the same token key with a different signature is
2991 : double-spending and must be refused */
2992 1 : TEST_RET_ON_FAIL (test_insert_used_token (
2993 : cls,
2994 : &use_pub,
2995 : &other_sig,
2996 : &issue_sig,
2997 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS,
2998 : false));
2999 1 : TEST_RET_ON_FAIL (test_token_family_counters (cls,
3000 : 2,
3001 : 1));
3002 : /* Deleting the token family cascades to the token family keys. An
3003 : order created before the deletion may still be paid; the missing
3004 : key must then be reported to the caller (which turns it into a 404
3005 : TOKEN_KEY_UNKNOWN) instead of being an internal hard error. */
3006 1 : TEST_SET_INSTANCE (cls->instance.instance.id,
3007 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
3008 1 : TEST_COND_RET_ON_FAIL (
3009 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
3010 : TALER_MERCHANTDB_delete_token_family (pg,
3011 : cls->instance.instance.id,
3012 : cls->family.slug),
3013 : "Delete token family failed\n");
3014 1 : TEST_RET_ON_FAIL (test_insert_issued_token (
3015 : cls,
3016 : &blind_sig1,
3017 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS,
3018 : true));
3019 1 : TEST_RET_ON_FAIL (test_insert_used_token (
3020 : cls,
3021 : &use_pub,
3022 : &use_sig,
3023 : &issue_sig,
3024 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS,
3025 : true));
3026 1 : return 0;
3027 : }
3028 :
3029 :
3030 : /**
3031 : * Takes care of token testing.
3032 : *
3033 : * @return 0 on success, 1 otherwise.
3034 : */
3035 : static int
3036 1 : test_tokens (void)
3037 : {
3038 : struct TestTokens_Closure test_cls;
3039 : int test_result;
3040 :
3041 1 : pre_test_tokens (&test_cls);
3042 1 : test_result = run_test_tokens (&test_cls);
3043 1 : post_test_tokens (&test_cls);
3044 1 : return test_result;
3045 : }
3046 :
3047 :
3048 : /* ********** Orders ********** */
3049 :
3050 :
3051 : /**
3052 : * Container for order data
3053 : */
3054 : struct OrderData
3055 : {
3056 : /**
3057 : * The id of the order
3058 : */
3059 : const char *id;
3060 :
3061 : /**
3062 : * The pay deadline for the order
3063 : */
3064 : struct GNUNET_TIME_Timestamp pay_deadline;
3065 :
3066 : /**
3067 : * The contract of the order
3068 : */
3069 : json_t *contract;
3070 :
3071 : /**
3072 : * The claim token for the order.
3073 : */
3074 : struct TALER_ClaimTokenP claim_token;
3075 : };
3076 :
3077 :
3078 : /**
3079 : * Builds an order for testing.
3080 : *
3081 : * @param order_id the identifier to use for the order.
3082 : * @param order the container to fill with data.
3083 : */
3084 : static void
3085 76 : make_order (const char *order_id,
3086 : struct OrderData *order)
3087 : {
3088 : struct GNUNET_TIME_Timestamp refund_deadline;
3089 :
3090 76 : order->id = order_id;
3091 76 : order->contract = json_object ();
3092 76 : GNUNET_assert (NULL != order->contract);
3093 76 : order->pay_deadline = GNUNET_TIME_relative_to_timestamp (
3094 : GNUNET_TIME_UNIT_DAYS);
3095 76 : GNUNET_CRYPTO_random_block (&order->claim_token,
3096 : sizeof (order->claim_token));
3097 76 : refund_deadline = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS);
3098 76 : GNUNET_assert (0 ==
3099 : json_object_set_new (order->contract,
3100 : "fulfillment_url",
3101 : json_string ("a")));
3102 76 : GNUNET_assert (0 ==
3103 : json_object_set_new (order->contract,
3104 : "summary",
3105 : json_string ("Test order")));
3106 76 : GNUNET_assert (0 ==
3107 : json_object_set_new (order->contract,
3108 : "order_id",
3109 : json_string (order_id)));
3110 76 : GNUNET_assert (0 ==
3111 : json_object_set_new (
3112 : order->contract,
3113 : "pay_deadline",
3114 : GNUNET_JSON_from_timestamp (order->pay_deadline))
3115 : );
3116 76 : GNUNET_assert (0 ==
3117 : json_object_set_new (order->contract,
3118 : "refund_deadline",
3119 : GNUNET_JSON_from_timestamp (
3120 : refund_deadline)));
3121 76 : }
3122 :
3123 :
3124 : /**
3125 : * Frees memory associated with an order.
3126 : *
3127 : * @param the order to free.
3128 : */
3129 : static void
3130 74 : free_order_data (struct OrderData *order)
3131 : {
3132 74 : json_decref (order->contract);
3133 74 : }
3134 :
3135 :
3136 : /**
3137 : * Tests inserting an order into the database.
3138 : *
3139 : * @param instance the instance to insert the order for.
3140 : * @param order the order to insert.
3141 : * @param expected_result the value we expect the db to return.
3142 : * @return 0 on success, 1 otherwise.
3143 : */
3144 : static int
3145 78 : test_insert_order (const struct InstanceData *instance,
3146 : const struct OrderData *order,
3147 : enum GNUNET_DB_QueryStatus expected_result)
3148 : {
3149 : struct TALER_MerchantPostDataHashP h_post;
3150 :
3151 78 : memset (&h_post,
3152 : 42,
3153 : sizeof (h_post));
3154 78 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
3155 78 : TEST_COND_RET_ON_FAIL (expected_result ==
3156 : TALER_MERCHANTDB_insert_order (pg,
3157 : instance->instance.id,
3158 : order->id,
3159 : NULL, /* session_id */
3160 : &h_post,
3161 : order->pay_deadline,
3162 : &order->claim_token,
3163 : order->contract,
3164 : NULL,
3165 : 0),
3166 : "Insert order failed\n");
3167 78 : return 0;
3168 : }
3169 :
3170 :
3171 : /**
3172 : * Tests looking up an order in the database.
3173 : *
3174 : * @param instance the instance to lookup from.
3175 : * @param order the order that should be looked up.
3176 : * @return 0 on success, 1 otherwise.
3177 : */
3178 : static int
3179 1 : test_lookup_order (const struct InstanceData *instance,
3180 : const struct OrderData *order)
3181 : {
3182 : struct TALER_ClaimTokenP ct;
3183 1 : json_t *lookup_terms = NULL;
3184 : struct TALER_MerchantPostDataHashP oh;
3185 : struct TALER_MerchantPostDataHashP wh;
3186 :
3187 1 : memset (&wh,
3188 : 42,
3189 : sizeof (wh));
3190 1 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
3191 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
3192 1 : TALER_MERCHANTDB_get_order (pg,
3193 1 : instance->instance.id,
3194 1 : order->id,
3195 : &ct,
3196 : &oh,
3197 : &lookup_terms))
3198 : {
3199 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3200 : "Lookup order failed\n");
3201 0 : if (NULL != lookup_terms)
3202 0 : json_decref (lookup_terms);
3203 0 : return 1;
3204 : }
3205 1 : if ( (1 != json_equal (order->contract,
3206 1 : lookup_terms)) ||
3207 1 : (0 != GNUNET_memcmp (&order->claim_token,
3208 1 : &ct)) ||
3209 1 : (0 != GNUNET_memcmp (&oh,
3210 : &wh)) )
3211 : {
3212 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3213 : "Lookup order failed: incorrect order returned\n");
3214 0 : if (NULL != lookup_terms)
3215 0 : json_decref (lookup_terms);
3216 0 : return 1;
3217 : }
3218 1 : json_decref (lookup_terms);
3219 1 : return 0;
3220 : }
3221 :
3222 :
3223 : /**
3224 : * Closure for testing order lookup
3225 : */
3226 : struct TestLookupOrders_Closure
3227 : {
3228 : /**
3229 : * Number of orders to compare to
3230 : */
3231 : unsigned int orders_to_cmp_length;
3232 :
3233 : /**
3234 : * Pointer to (ordered) array of order ids
3235 : */
3236 : const struct OrderData *orders_to_cmp;
3237 :
3238 : /**
3239 : * Pointer to array of bools indicating matches in the correct index
3240 : */
3241 : bool *results_match;
3242 :
3243 : /**
3244 : * Total number of results returned
3245 : */
3246 : unsigned int results_length;
3247 : };
3248 :
3249 :
3250 : /**
3251 : * Called after @e test_lookup_orders.
3252 : *
3253 : * @param cls the lookup closure.
3254 : * @param order_id the identifier of the order found.
3255 : * @param order_serial the row number of the order found.
3256 : * @param timestamp when the order was added to the database.
3257 : */
3258 : static void
3259 1031 : lookup_orders_cb (void *cls,
3260 : const char *order_id,
3261 : uint64_t order_serial,
3262 : struct GNUNET_TIME_Timestamp timestamp)
3263 : {
3264 1031 : struct TestLookupOrders_Closure *cmp = cls;
3265 : unsigned int i;
3266 :
3267 1031 : if (NULL == cmp)
3268 0 : return;
3269 1031 : i = cmp->results_length;
3270 1031 : cmp->results_length += 1;
3271 1031 : if (cmp->orders_to_cmp_length > i)
3272 : {
3273 : /* Compare the orders */
3274 1031 : if (0 == strcmp (cmp->orders_to_cmp[i].id,
3275 : order_id))
3276 1031 : cmp->results_match[i] = true;
3277 : else
3278 0 : cmp->results_match[i] = false;
3279 : }
3280 : }
3281 :
3282 :
3283 : /**
3284 : * Tests looking up orders for an instance.
3285 : *
3286 : * @param instance the instance.
3287 : * @param filter the filters applied on the lookup.
3288 : * @param orders_length the number of orders we expect to find.
3289 : * @param orders the orders we expect to find.
3290 : * @return 0 on success, 1 otherwise.
3291 : */
3292 : static int
3293 63 : test_lookup_orders (const struct InstanceData *instance,
3294 : const struct TALER_MERCHANTDB_OrderFilter *filter,
3295 : unsigned int orders_length,
3296 : const struct OrderData *orders)
3297 63 : {
3298 63 : bool results_match[GNUNET_NZL (orders_length)];
3299 63 : struct TestLookupOrders_Closure cls = {
3300 : .orders_to_cmp_length = orders_length,
3301 : .orders_to_cmp = orders,
3302 : .results_match = results_match,
3303 : .results_length = 0
3304 : };
3305 63 : memset (results_match,
3306 : 0,
3307 : sizeof (bool) * orders_length);
3308 63 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
3309 63 : if (0 > TALER_MERCHANTDB_iterate_orders (pg,
3310 63 : instance->instance.id,
3311 : filter,
3312 : &lookup_orders_cb,
3313 : &cls))
3314 : {
3315 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3316 : "Lookup orders failed\n");
3317 0 : return 1;
3318 : }
3319 63 : if (orders_length != cls.results_length)
3320 : {
3321 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3322 : "Lookup orders failed: incorrect number of results (%d)\n",
3323 : cls.results_length);
3324 0 : return 1;
3325 : }
3326 1094 : for (unsigned int i = 0; orders_length > i; ++i)
3327 : {
3328 1031 : if (false == cls.results_match[i])
3329 : {
3330 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3331 : "Lookup orders failed: mismatched data (index %d)\n",
3332 : i);
3333 0 : return 1;
3334 : }
3335 : }
3336 63 : return 0;
3337 : }
3338 :
3339 :
3340 : /**
3341 : * Container for data used for looking up the row number of an order.
3342 : */
3343 : struct LookupOrderSerial_Closure
3344 : {
3345 : /**
3346 : * The order that is being looked up.
3347 : */
3348 : const struct OrderData *order;
3349 :
3350 : /**
3351 : * The serial of the order that was found.
3352 : */
3353 : uint64_t serial;
3354 : };
3355 :
3356 :
3357 : /**
3358 : * Called after @e test_lookup_orders.
3359 : *
3360 : * @param cls the lookup closure.
3361 : * @param order_id the identifier of the order found.
3362 : * @param order_serial the row number of the order found.
3363 : * @param timestamp when the order was added to the database.
3364 : */
3365 : static void
3366 2105 : get_order_serial_cb (void *cls,
3367 : const char *order_id,
3368 : uint64_t order_serial,
3369 : struct GNUNET_TIME_Timestamp timestamp)
3370 : {
3371 2105 : struct LookupOrderSerial_Closure *lookup_cls = cls;
3372 2105 : if (NULL == lookup_cls)
3373 0 : return;
3374 2105 : if (0 == strcmp (lookup_cls->order->id,
3375 : order_id))
3376 77 : lookup_cls->serial = order_serial;
3377 : }
3378 :
3379 :
3380 : /**
3381 : * Convenience function for getting the row number of an order.
3382 : *
3383 : * @param instance the instance to look up from.
3384 : * @param order the order to lookup the serial for.
3385 : * @return the row number of the order.
3386 : */
3387 : static uint64_t
3388 77 : get_order_serial (const struct InstanceData *instance,
3389 : const struct OrderData *order)
3390 : {
3391 77 : struct LookupOrderSerial_Closure lookup_cls = {
3392 : .order = order,
3393 : .serial = 0
3394 : };
3395 77 : struct TALER_MERCHANTDB_OrderFilter filter = {
3396 : .paid = TALER_EXCHANGE_YNA_ALL,
3397 : .refunded = TALER_EXCHANGE_YNA_ALL,
3398 : .wired = TALER_EXCHANGE_YNA_ALL,
3399 : .expired = TALER_EXCHANGE_YNA_ALL,
3400 : .date = GNUNET_TIME_UNIT_ZERO_TS,
3401 : .start_row = 0,
3402 : .delta = 256
3403 : };
3404 :
3405 77 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
3406 : TALER_MERCHANTDB_set_instance (pg,
3407 : instance->instance.id));
3408 77 : GNUNET_assert (0 < TALER_MERCHANTDB_iterate_orders (pg,
3409 : instance->instance.id,
3410 : &filter,
3411 : &get_order_serial_cb,
3412 : &lookup_cls));
3413 77 : GNUNET_assert (0 != lookup_cls.serial);
3414 :
3415 77 : return lookup_cls.serial;
3416 : }
3417 :
3418 :
3419 : /**
3420 : * Tests deleting an order from the database.
3421 : *
3422 : * @param instance the instance to delete the order from.
3423 : * @param order the order to delete.
3424 : * @param expected_result the result we expect to receive.
3425 : * @return 0 on success, 1 otherwise.
3426 : */
3427 : static int
3428 4 : test_delete_order (const struct InstanceData *instance,
3429 : const struct OrderData *order,
3430 : enum GNUNET_DB_QueryStatus expected_result)
3431 : {
3432 4 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
3433 4 : TEST_COND_RET_ON_FAIL (expected_result ==
3434 : TALER_MERCHANTDB_delete_order (pg,
3435 : instance->instance.id,
3436 : order->id,
3437 : false),
3438 : "Delete order failed\n");
3439 4 : return 0;
3440 : }
3441 :
3442 :
3443 : /**
3444 : * Runs @a sql directly on the database connection of the test. Used to
3445 : * install the fault-injection trigger of #test_delete_order_atomic().
3446 : *
3447 : * @param sql the SQL statement(s) to execute
3448 : * @return 0 on success, 1 otherwise.
3449 : */
3450 : static int
3451 2 : orders_exec_sql (const char *sql)
3452 : {
3453 2 : struct GNUNET_PQ_ExecuteStatement es[] = {
3454 2 : GNUNET_PQ_make_execute (sql),
3455 : GNUNET_PQ_EXECUTE_STATEMENT_END
3456 : };
3457 :
3458 2 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
3459 : GNUNET_PQ_exec_statements (pg->conn,
3460 : es),
3461 : "Direct SQL execution failed\n");
3462 2 : return 0;
3463 : }
3464 :
3465 :
3466 : /**
3467 : * Tests that a forced order deletion removes the row in
3468 : * merchant_orders and the row in merchant_contract_terms atomically.
3469 : *
3470 : * The caller of TALER_MERCHANTDB_delete_order() does not open a
3471 : * transaction, so doing this in two statements would run them in two
3472 : * separate autocommit transactions: the first would stay committed
3473 : * when the second one fails, leaving orphaned contract terms behind.
3474 : * We provoke exactly that by installing a trigger that makes the
3475 : * deletion of the contract terms fail, and then check that the order
3476 : * itself survived.
3477 : *
3478 : * @param instance the instance to delete the order from.
3479 : * @param order the order to delete; must be claimed and unpaid.
3480 : * @return 0 on success, 1 otherwise.
3481 : */
3482 : static int
3483 1 : test_delete_order_atomic (const struct InstanceData *instance,
3484 : const struct OrderData *order)
3485 : {
3486 : struct TALER_MerchantPostDataHashP h_post_data;
3487 :
3488 1 : TEST_SET_INSTANCE (instance->instance.id,
3489 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
3490 1 : TEST_RET_ON_FAIL (orders_exec_sql (
3491 : "CREATE FUNCTION test_block_ct_delete ()"
3492 : " RETURNS TRIGGER"
3493 : " LANGUAGE plpgsql"
3494 : " AS $$"
3495 : " BEGIN"
3496 : " RAISE EXCEPTION"
3497 : " 'simulated failure deleting contract terms';"
3498 : " END $$;"
3499 : "CREATE TRIGGER test_block_ct_delete_trigger"
3500 : " BEFORE DELETE ON merchant_contract_terms"
3501 : " FOR EACH ROW"
3502 : " EXECUTE FUNCTION test_block_ct_delete ();"));
3503 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_HARD_ERROR ==
3504 : TALER_MERCHANTDB_delete_order (pg,
3505 : instance->instance.id,
3506 : order->id,
3507 : true),
3508 : "Deleting the order should have failed\n");
3509 1 : TEST_RET_ON_FAIL (orders_exec_sql (
3510 : "DROP TRIGGER test_block_ct_delete_trigger"
3511 : " ON merchant_contract_terms;"
3512 : "DROP FUNCTION test_block_ct_delete ();"));
3513 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
3514 : TALER_MERCHANTDB_get_order (pg,
3515 : instance->instance.id,
3516 : order->id,
3517 : NULL,
3518 : &h_post_data,
3519 : NULL),
3520 : "Order was deleted even though deleting its"
3521 : " contract terms failed\n");
3522 : /* Without the trigger in the way, both rows must now go. */
3523 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
3524 : TALER_MERCHANTDB_delete_order (pg,
3525 : instance->instance.id,
3526 : order->id,
3527 : true),
3528 : "Forced deletion of a claimed, unpaid order failed\n");
3529 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
3530 : TALER_MERCHANTDB_get_order (pg,
3531 : instance->instance.id,
3532 : order->id,
3533 : NULL,
3534 : &h_post_data,
3535 : NULL),
3536 : "Order survived its deletion\n");
3537 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
3538 : TALER_MERCHANTDB_get_contract_terms (
3539 : pg,
3540 : instance->instance.id,
3541 : order->id,
3542 : NULL,
3543 : NULL,
3544 : NULL),
3545 : "Contract terms survived the order deletion\n");
3546 1 : return 0;
3547 : }
3548 :
3549 :
3550 : /**
3551 : * Test inserting contract terms for an order.
3552 : *
3553 : * @param instance the instance.
3554 : * @param order the order containing the contract terms.
3555 : * @param expected_result the result we expect to receive.
3556 : * @return 0 on success, 1 otherwise.
3557 : */
3558 : static int
3559 45 : test_insert_contract_terms (const struct InstanceData *instance,
3560 : const struct OrderData *order,
3561 : enum GNUNET_DB_QueryStatus expected_result)
3562 : {
3563 : uint64_t os;
3564 :
3565 45 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
3566 45 : TEST_COND_RET_ON_FAIL (expected_result ==
3567 : TALER_MERCHANTDB_insert_contract_terms (pg,
3568 : instance->instance.id,
3569 : order->id,
3570 : order->contract,
3571 : &os),
3572 : "Insert contract terms failed\n");
3573 45 : return 0;
3574 : }
3575 :
3576 :
3577 : /**
3578 : * Test updating contract terms for an order.
3579 : *
3580 : * @param instance the instance.
3581 : * @param order the order containing the contract terms.
3582 : * @param expected_result the result we expect to receive.
3583 : * @return 0 on success, 1 otherwise.
3584 : */
3585 : static int
3586 1 : test_update_contract_terms (const struct InstanceData *instance,
3587 : const struct OrderData *order,
3588 : enum GNUNET_DB_QueryStatus expected_result)
3589 : {
3590 1 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
3591 1 : TEST_COND_RET_ON_FAIL (expected_result ==
3592 : TALER_MERCHANTDB_update_contract_terms (pg,
3593 : instance->instance.id,
3594 : order->id,
3595 : order->contract),
3596 : "Update contract terms failed\n");
3597 1 : return 0;
3598 : }
3599 :
3600 :
3601 : /**
3602 : * Tests lookup of contract terms
3603 : *
3604 : * @param instance the instance to lookup from.
3605 : * @param order the order to lookup for.
3606 : * @return 0 on success, 1 otherwise.
3607 : */
3608 : static int
3609 2 : test_lookup_contract_terms (const struct InstanceData *instance,
3610 : const struct OrderData *order)
3611 : {
3612 2 : json_t *contract = NULL;
3613 : uint64_t order_serial;
3614 :
3615 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
3616 2 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
3617 2 : TALER_MERCHANTDB_get_contract_terms (pg,
3618 2 : instance->instance.id,
3619 2 : order->id,
3620 : &contract,
3621 : &order_serial,
3622 : NULL))
3623 : {
3624 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3625 : "Lookup contract terms failed\n");
3626 0 : GNUNET_assert (NULL == contract);
3627 0 : return 1;
3628 : }
3629 2 : if (1 != json_equal (order->contract,
3630 : contract))
3631 : {
3632 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3633 : "Lookup contract terms failed: mismatched data\n");
3634 0 : json_decref (contract);
3635 0 : return 1;
3636 : }
3637 2 : json_decref (contract);
3638 2 : return 0;
3639 : }
3640 :
3641 :
3642 : /**
3643 : * Tests deleting contract terms for an order.
3644 : *
3645 : * @param instance the instance to delete from.
3646 : * @param order the order whose contract terms we should delete.
3647 : * @param legal_expiration how long we must wait after creating an order to delete it
3648 : * @param expected_result the result we expect to receive.
3649 : * @return 0 on success, 1 otherwise.
3650 : */
3651 : static int
3652 3 : test_delete_contract_terms (const struct InstanceData *instance,
3653 : const struct OrderData *order,
3654 : struct GNUNET_TIME_Relative legal_expiration,
3655 : enum GNUNET_DB_QueryStatus expected_result)
3656 : {
3657 3 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
3658 3 : TEST_COND_RET_ON_FAIL (expected_result ==
3659 : TALER_MERCHANTDB_delete_contract_terms (pg,
3660 : instance->instance.id,
3661 : order->id,
3662 : legal_expiration),
3663 : "Delete contract terms failed\n");
3664 3 : return 0;
3665 : }
3666 :
3667 :
3668 : /**
3669 : * Test marking a contract as paid in the database.
3670 : *
3671 : * @param instance the instance to use.
3672 : * @param order the order whose contract to use.
3673 : * @param expected_result the result we expect to receive.
3674 : * @return 0 on success, 1 otherwise.
3675 : */
3676 : static int
3677 25 : test_mark_contract_paid (const struct InstanceData *instance,
3678 : const struct OrderData *order,
3679 : enum GNUNET_DB_QueryStatus expected_result)
3680 : {
3681 : struct TALER_PrivateContractHashP h_contract_terms;
3682 :
3683 25 : GNUNET_assert (GNUNET_OK ==
3684 : TALER_JSON_contract_hash (order->contract,
3685 : &h_contract_terms));
3686 25 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
3687 25 : TEST_COND_RET_ON_FAIL (expected_result ==
3688 : TALER_MERCHANTDB_update_to_contract_terms_paid (pg,
3689 : instance->instance.id,
3690 : &h_contract_terms,
3691 : "test_orders_session",
3692 : -1),
3693 : "Mark contract paid failed\n");
3694 25 : return 0;
3695 : }
3696 :
3697 :
3698 : /**
3699 : * Tests looking up the status of an order.
3700 : *
3701 : * @param instance the instance to lookup from.
3702 : * @param order the order to lookup.
3703 : * @param expected_paid whether the order was paid or not.
3704 : * @return 0 on success, 1 otherwise.
3705 : */
3706 : static int
3707 2 : test_lookup_order_status (const struct InstanceData *instance,
3708 : const struct OrderData *order,
3709 : bool expected_paid)
3710 : {
3711 : struct TALER_PrivateContractHashP h_contract_terms_expected;
3712 : struct TALER_PrivateContractHashP h_contract_terms;
3713 2 : bool order_paid = false;
3714 :
3715 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
3716 2 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
3717 2 : TALER_MERCHANTDB_get_order_status (pg,
3718 2 : instance->instance.id,
3719 2 : order->id,
3720 : &h_contract_terms,
3721 : &order_paid))
3722 : {
3723 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3724 : "Lookup order status failed\n");
3725 0 : return 1;
3726 : }
3727 2 : GNUNET_assert (GNUNET_OK ==
3728 : TALER_JSON_contract_hash (order->contract,
3729 : &h_contract_terms_expected));
3730 2 : if ((expected_paid != order_paid) ||
3731 2 : (0 != GNUNET_memcmp (&h_contract_terms,
3732 : &h_contract_terms_expected)))
3733 : {
3734 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3735 : "Lookup order status/deposit failed: mismatched data\n");
3736 0 : return 1;
3737 : }
3738 2 : return 0;
3739 : }
3740 :
3741 :
3742 : /**
3743 : * Test looking up an order by its fulfillment.
3744 : *
3745 : * @param instance the instance to lookup from.
3746 : * @param order the order to lookup.
3747 : * @param the session id associated with the payment.
3748 : * @return 0 on success, 1 otherwise.
3749 : */
3750 : static int
3751 1 : test_lookup_order_by_fulfillment (const struct InstanceData *instance,
3752 : const struct OrderData *order,
3753 : const char *session_id)
3754 : {
3755 : char *order_id;
3756 : const char *fulfillment_url =
3757 1 : json_string_value (json_object_get (order->contract,
3758 : "fulfillment_url"));
3759 1 : GNUNET_assert (NULL != fulfillment_url);
3760 1 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
3761 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
3762 1 : TALER_MERCHANTDB_get_order_by_fulfillment (pg,
3763 1 : instance->instance.id,
3764 : fulfillment_url,
3765 : session_id,
3766 : false,
3767 : &order_id))
3768 : {
3769 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3770 : "Lookup order by fulfillment failed\n");
3771 0 : GNUNET_free (order_id);
3772 0 : return 1;
3773 : }
3774 1 : if (0 != strcmp (order->id,
3775 : order_id))
3776 : {
3777 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3778 : "Lookup order by fulfillment failed\n");
3779 0 : GNUNET_free (order_id);
3780 0 : return 1;
3781 : }
3782 1 : GNUNET_free (order_id);
3783 1 : return 0;
3784 : }
3785 :
3786 :
3787 : /**
3788 : * Test looking up the status of an order.
3789 : *
3790 : * @param order_id the row of the order in the database.
3791 : * @param session_id the session id associated with the payment.
3792 : * @param expected_paid whether the order was paid or not.
3793 : * @param expected_wired whether the order was wired or not.
3794 : * @return 0 on success, 1 otherwise.
3795 : */
3796 : static int
3797 7 : test_lookup_payment_status (const char *instance_id,
3798 : const char *order_id,
3799 : const char *session_id,
3800 : bool expected_paid,
3801 : bool expected_wired)
3802 : {
3803 : bool paid;
3804 : bool wired;
3805 : bool matches;
3806 : uint64_t os;
3807 : int16_t choice_index;
3808 :
3809 7 : TEST_SET_INSTANCE (instance_id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
3810 7 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
3811 : TALER_MERCHANTDB_get_contract_terms_status (pg,
3812 : instance_id,
3813 : order_id,
3814 : session_id,
3815 : NULL,
3816 : &os,
3817 : &paid,
3818 : &wired,
3819 : &matches,
3820 : NULL,
3821 : &choice_index),
3822 : "Lookup payment status failed\n");
3823 7 : if ( (NULL != session_id) && (! matches) )
3824 : {
3825 1 : paid = false;
3826 1 : wired = false;
3827 : }
3828 7 : if (expected_wired != wired)
3829 : {
3830 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3831 : "Lookup payment status for %s/%s failed: wired status is wrong (expected %d got %d)\n",
3832 : instance_id,
3833 : order_id,
3834 : expected_wired,
3835 : wired);
3836 0 : return 1;
3837 : }
3838 7 : if (expected_paid != paid)
3839 : {
3840 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3841 : "Lookup payment status failed: paid status is wrong\n");
3842 0 : return 1;
3843 : }
3844 7 : return 0;
3845 : }
3846 :
3847 :
3848 : /**
3849 : * Test marking an order as being wired.
3850 : *
3851 : * @param order_id the row of the order in the database.
3852 : * @param expected_result the result we expect the DB to return.
3853 : * @return 0 on success, 1 otherwise.
3854 : */
3855 : static int
3856 18 : test_mark_order_wired (uint64_t order_id,
3857 : enum GNUNET_DB_QueryStatus expected_result)
3858 : {
3859 18 : TEST_COND_RET_ON_FAIL (expected_result ==
3860 : TALER_MERCHANTDB_update_to_contract_terms_wired (pg,
3861 : order_id),
3862 : "Mark order wired failed\n");
3863 18 : return 0;
3864 : }
3865 :
3866 :
3867 : /**
3868 : * Closure for order tests.
3869 : */
3870 : struct TestOrders_Closure
3871 : {
3872 : /**
3873 : * The instance to use for the order tests.
3874 : */
3875 : struct InstanceData instance;
3876 :
3877 : /**
3878 : * A product to use for the order tests.
3879 : */
3880 : struct ProductData product;
3881 :
3882 : /**
3883 : * The array of orders
3884 : */
3885 : struct OrderData orders[3];
3886 : };
3887 :
3888 :
3889 : /**
3890 : * Initializes order test data.
3891 : *
3892 : * @param cls the order test closure.
3893 : */
3894 : static void
3895 1 : pre_test_orders (struct TestOrders_Closure *cls)
3896 : {
3897 : /* Instance */
3898 1 : make_instance ("test_inst_orders",
3899 : &cls->instance);
3900 :
3901 : /* Product */
3902 1 : make_product ("test_orders_pd_0",
3903 : &cls->product);
3904 :
3905 : /* Orders */
3906 1 : make_order ("test_orders_od_0",
3907 : &cls->orders[0]);
3908 1 : make_order ("test_orders_od_1",
3909 : &cls->orders[1]);
3910 1 : make_order ("test_orders_od_2",
3911 : &cls->orders[2]);
3912 :
3913 1 : GNUNET_assert (0 ==
3914 : json_object_set_new (cls->orders[1].contract,
3915 : "other_field",
3916 : json_string ("Second contract")));
3917 :
3918 1 : cls->orders[2].pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
3919 1 : GNUNET_assert (0 ==
3920 : json_object_set_new (
3921 : cls->orders[2].contract,
3922 : "pay_deadline",
3923 : GNUNET_JSON_from_timestamp (cls->orders[2].pay_deadline)));
3924 1 : }
3925 :
3926 :
3927 : /**
3928 : * Frees memory after order tests.
3929 : *
3930 : * @param cls the order test closure.
3931 : */
3932 : static void
3933 1 : post_test_orders (struct TestOrders_Closure *cls)
3934 : {
3935 1 : free_instance_data (&cls->instance);
3936 1 : free_product_data (&cls->product);
3937 1 : free_order_data (&cls->orders[0]);
3938 1 : free_order_data (&cls->orders[1]);
3939 1 : free_order_data (&cls->orders[2]);
3940 1 : }
3941 :
3942 :
3943 : /**
3944 : * Run the tests for orders.
3945 : *
3946 : * @param cls the order test closure.
3947 : * @return 0 on success, 1 on failure.
3948 : */
3949 : static int
3950 1 : run_test_orders (struct TestOrders_Closure *cls)
3951 : {
3952 1 : struct TALER_MERCHANTDB_OrderFilter filter = {
3953 : .paid = TALER_EXCHANGE_YNA_ALL,
3954 : .refunded = TALER_EXCHANGE_YNA_ALL,
3955 : .wired = TALER_EXCHANGE_YNA_ALL,
3956 : .expired = TALER_EXCHANGE_YNA_ALL,
3957 : .date = GNUNET_TIME_UNIT_ZERO_TS,
3958 : .start_row = 0,
3959 : .delta = 8
3960 : };
3961 : uint64_t serial;
3962 :
3963 : /* Insert the instance */
3964 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
3965 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
3966 : /* Test inserting an order */
3967 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
3968 : &cls->orders[0],
3969 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
3970 : /* Test double insert fails */
3971 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
3972 : &cls->orders[0],
3973 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
3974 : /* Test lookup order */
3975 1 : TEST_RET_ON_FAIL (test_lookup_order (&cls->instance,
3976 : &cls->orders[0]));
3977 : /* Make sure it fails correctly for nonexistent orders */
3978 : {
3979 : struct TALER_MerchantPostDataHashP unused;
3980 :
3981 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
3982 1 : TALER_MERCHANTDB_get_order (pg,
3983 1 : cls->instance.instance.id,
3984 : cls->orders[1].id,
3985 : NULL,
3986 : &unused,
3987 : NULL))
3988 : {
3989 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
3990 : "Lookup order failed\n");
3991 0 : return 1;
3992 : }
3993 : }
3994 : /* Test lookups on multiple orders */
3995 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
3996 : &cls->orders[1],
3997 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
3998 1 : filter.expired = TALER_EXCHANGE_YNA_NO;
3999 1 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
4000 : &filter,
4001 : 2,
4002 : cls->orders));
4003 1 : filter.expired = TALER_EXCHANGE_YNA_YES;
4004 1 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
4005 : &filter,
4006 : 0,
4007 : NULL));
4008 1 : filter.expired = TALER_EXCHANGE_YNA_ALL;
4009 1 : serial = get_order_serial (&cls->instance,
4010 1 : &cls->orders[0]);
4011 1 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
4012 : &filter,
4013 : 2,
4014 : cls->orders));
4015 : /* Test inserting contract terms */
4016 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
4017 : &cls->orders[0],
4018 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4019 : /* Test double insert fails */
4020 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
4021 : &cls->orders[0],
4022 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
4023 : /* Test order lock */
4024 1 : TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
4025 : &cls->product,
4026 : 0,
4027 : NULL,
4028 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
4029 : false,
4030 : false,
4031 : -1));
4032 1 : if (1 != TALER_MERCHANTDB_insert_order_lock (pg,
4033 1 : cls->instance.instance.id,
4034 : cls->orders[0].id,
4035 : cls->product.id,
4036 : 5,
4037 : 0))
4038 : {
4039 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4040 : "Insert order lock failed\n");
4041 0 : return 1;
4042 : }
4043 : /* Test lookup contract terms */
4044 1 : TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance,
4045 : &cls->orders[0]));
4046 : /* Test lookup fails for nonexistent contract terms */
4047 : {
4048 1 : json_t *lookup_contract = NULL;
4049 : uint64_t lookup_order_serial;
4050 :
4051 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
4052 1 : TALER_MERCHANTDB_get_contract_terms (pg,
4053 1 : cls->instance.instance.id,
4054 : cls->orders[1].id,
4055 : &lookup_contract,
4056 : &lookup_order_serial,
4057 : NULL))
4058 : {
4059 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4060 : "Lookup contract terms failed\n");
4061 0 : GNUNET_assert (NULL == lookup_contract);
4062 0 : return 1;
4063 : }
4064 : }
4065 : /* Test update contract terms */
4066 1 : GNUNET_assert (0 ==
4067 : json_object_set_new (cls->orders[0].contract,
4068 : "some_new_field",
4069 : json_string ("another value")));
4070 1 : TEST_RET_ON_FAIL (test_update_contract_terms (&cls->instance,
4071 : &cls->orders[0],
4072 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4073 1 : TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance,
4074 : &cls->orders[0]));
4075 : /* Test lookup order status */
4076 1 : TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance,
4077 : &cls->orders[0],
4078 : false));
4079 : {
4080 : struct TALER_PrivateContractHashP h_contract_terms;
4081 1 : bool order_paid = false;
4082 :
4083 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
4084 1 : TALER_MERCHANTDB_get_order_status (pg,
4085 1 : cls->instance.instance.id,
4086 : cls->orders[1].id,
4087 : &h_contract_terms,
4088 : &order_paid))
4089 : {
4090 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4091 : "Lookup order status failed\n");
4092 0 : return 1;
4093 : }
4094 : }
4095 : /* Test lookup payment status */
4096 1 : TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
4097 : cls->orders[0].id,
4098 : NULL,
4099 : false,
4100 : false));
4101 : /* Test lookup order status fails for nonexistent order */
4102 : {
4103 : struct TALER_PrivateContractHashP h_contract_terms;
4104 : bool order_paid;
4105 :
4106 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
4107 1 : TALER_MERCHANTDB_get_order_status (pg,
4108 1 : cls->instance.instance.id,
4109 : cls->orders[1].id,
4110 : &h_contract_terms,
4111 : &order_paid))
4112 : {
4113 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4114 : "Lookup order status failed\n");
4115 0 : return 1;
4116 : }
4117 : }
4118 : /* Test marking contracts as paid */
4119 1 : TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
4120 : &cls->orders[0],
4121 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4122 1 : TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
4123 : cls->orders[0].id,
4124 : NULL,
4125 : true,
4126 : false));
4127 1 : TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
4128 : cls->orders[0].id,
4129 : "test_orders_session",
4130 : true,
4131 : false));
4132 1 : TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
4133 : cls->orders[0].id,
4134 : "bad_session",
4135 : false,
4136 : false));
4137 : /* Test lookup order by fulfillment */
4138 1 : TEST_RET_ON_FAIL (test_lookup_order_by_fulfillment (&cls->instance,
4139 : &cls->orders[0],
4140 : "test_orders_session"));
4141 : {
4142 : char *order_id;
4143 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
4144 1 : TALER_MERCHANTDB_get_order_by_fulfillment (pg,
4145 1 : cls->instance.instance.id,
4146 : "fulfillment_url",
4147 : "test_orders_session",
4148 : false,
4149 : &order_id))
4150 : {
4151 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4152 : "Lookup order by fulfillment failed\n");
4153 0 : GNUNET_free (order_id);
4154 0 : return 1;
4155 : }
4156 : }
4157 : /* Test mark as paid fails for nonexistent order */
4158 1 : TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
4159 : &cls->orders[1],
4160 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
4161 1 : TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance,
4162 : &cls->orders[0],
4163 : true));
4164 1 : filter.paid = TALER_EXCHANGE_YNA_ALL;
4165 1 : filter.expired = TALER_EXCHANGE_YNA_YES;
4166 1 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
4167 : &filter,
4168 : 0,
4169 : NULL));
4170 1 : filter.expired = TALER_EXCHANGE_YNA_ALL;
4171 1 : filter.paid = TALER_EXCHANGE_YNA_YES;
4172 1 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
4173 : &filter,
4174 : 1,
4175 : cls->orders));
4176 : /* Test marking orders as wired */
4177 1 : TEST_RET_ON_FAIL (test_mark_order_wired (serial,
4178 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT))
4179 : ;
4180 1 : TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
4181 : cls->orders[0].id,
4182 : NULL,
4183 : true,
4184 : true));
4185 1 : TEST_RET_ON_FAIL (test_mark_order_wired (1007,
4186 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS))
4187 : ;
4188 : /* If an order has been claimed and we aren't past
4189 : the pay deadline, we can't delete it. */
4190 1 : TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
4191 : &cls->orders[0],
4192 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
4193 : /* Test we can't delete before the legal expiration */
4194 1 : TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
4195 : &cls->orders[0],
4196 : GNUNET_TIME_UNIT_MONTHS,
4197 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
4198 : /* Test deleting contract terms */
4199 1 : TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
4200 : &cls->orders[0],
4201 : GNUNET_TIME_UNIT_ZERO,
4202 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4203 : /* Test we can't delete something that doesn't exist */
4204 1 : TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
4205 : &cls->orders[0],
4206 : GNUNET_TIME_UNIT_ZERO,
4207 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
4208 : /* Test delete order where we aren't past
4209 : the deadline, but the order is unclaimed. */
4210 1 : TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
4211 : &cls->orders[1],
4212 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4213 1 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
4214 : &filter,
4215 : 0,
4216 : NULL));
4217 : /* Test we can't delete something that doesn't exist */
4218 1 : TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
4219 : &cls->orders[1],
4220 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
4221 :
4222 : /* Test we can also delete a claimed order that's past the pay deadline. */
4223 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
4224 : &cls->orders[2],
4225 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4226 1 : filter.paid = TALER_EXCHANGE_YNA_ALL;
4227 1 : filter.expired = TALER_EXCHANGE_YNA_YES;
4228 1 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
4229 : &filter,
4230 : 1,
4231 : &cls->orders[2]));
4232 1 : filter.expired = TALER_EXCHANGE_YNA_NO;
4233 1 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
4234 : &filter,
4235 : 0,
4236 : NULL));
4237 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
4238 : &cls->orders[2],
4239 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4240 1 : filter.expired = TALER_EXCHANGE_YNA_YES;
4241 1 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
4242 : &filter,
4243 : 1,
4244 : &cls->orders[2]));
4245 1 : filter.expired = TALER_EXCHANGE_YNA_ALL;
4246 1 : TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
4247 : &cls->orders[2],
4248 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4249 :
4250 : /* Deleting an order and its contract terms must be atomic.
4251 : orders[1] was fully removed above, so we can re-create it, this
4252 : time claimed (=> with contract terms) and unpaid. */
4253 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
4254 : &cls->orders[1],
4255 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4256 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
4257 : &cls->orders[1],
4258 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
4259 1 : TEST_RET_ON_FAIL (test_delete_order_atomic (&cls->instance,
4260 : &cls->orders[1]));
4261 1 : return 0;
4262 : }
4263 :
4264 :
4265 : /**
4266 : * Does all tasks for testing orders.
4267 : *
4268 : * @return 0 when successful, 1 otherwise.
4269 : */
4270 : static int
4271 1 : test_orders (void)
4272 : {
4273 : struct TestOrders_Closure test_cls;
4274 : int test_result;
4275 :
4276 1 : pre_test_orders (&test_cls);
4277 1 : test_result = run_test_orders (&test_cls);
4278 1 : post_test_orders (&test_cls);
4279 1 : return test_result;
4280 : }
4281 :
4282 :
4283 : /* ********** Deposits ********** */
4284 :
4285 :
4286 : /**
4287 : * A container for exchange signing key data.
4288 : */
4289 : struct ExchangeSignkeyData
4290 : {
4291 : /**
4292 : * The master private key of the exchange.
4293 : */
4294 : struct TALER_MasterPrivateKeyP master_priv;
4295 :
4296 : /**
4297 : * The master public key of the exchange.
4298 : */
4299 : struct TALER_MasterPublicKeyP master_pub;
4300 :
4301 : /**
4302 : * A signature made with the master keys.
4303 : */
4304 : struct TALER_MasterSignatureP master_sig;
4305 :
4306 : /**
4307 : * The private key of the exchange.
4308 : */
4309 : struct TALER_ExchangePrivateKeyP exchange_priv;
4310 :
4311 : /**
4312 : * The public key of the exchange.
4313 : */
4314 : struct TALER_ExchangePublicKeyP exchange_pub;
4315 :
4316 : /**
4317 : * When the signing key becomes valid.
4318 : */
4319 : struct GNUNET_TIME_Timestamp start_date;
4320 :
4321 : /**
4322 : * When the signing key stops being used.
4323 : */
4324 : struct GNUNET_TIME_Timestamp expire_date;
4325 :
4326 : /**
4327 : * When the signing key becomes invalid for proof.
4328 : */
4329 : struct GNUNET_TIME_Timestamp end_date;
4330 : };
4331 :
4332 :
4333 : /**
4334 : * Creates an exchange signing key.
4335 : *
4336 : * @param signkey the signing key data to fill.
4337 : */
4338 : static void
4339 4 : make_exchange_signkey (struct ExchangeSignkeyData *signkey)
4340 : {
4341 4 : struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
4342 :
4343 4 : GNUNET_CRYPTO_eddsa_key_create (&signkey->exchange_priv.eddsa_priv);
4344 4 : GNUNET_CRYPTO_eddsa_key_get_public (&signkey->exchange_priv.eddsa_priv,
4345 : &signkey->exchange_pub.eddsa_pub);
4346 4 : GNUNET_CRYPTO_eddsa_key_create (&signkey->master_priv.eddsa_priv);
4347 4 : GNUNET_CRYPTO_eddsa_key_get_public (&signkey->master_priv.eddsa_priv,
4348 : &signkey->master_pub.eddsa_pub);
4349 4 : signkey->start_date = now;
4350 4 : signkey->expire_date = now;
4351 4 : signkey->end_date = now;
4352 4 : TALER_exchange_offline_signkey_validity_sign (
4353 4 : &signkey->exchange_pub,
4354 : signkey->start_date,
4355 : signkey->expire_date,
4356 : signkey->end_date,
4357 4 : &signkey->master_priv,
4358 : &signkey->master_sig);
4359 4 : }
4360 :
4361 :
4362 : /**
4363 : * A container for deposit data.
4364 : */
4365 : struct DepositData
4366 : {
4367 : /**
4368 : * When the deposit was made.
4369 : */
4370 : struct GNUNET_TIME_Timestamp timestamp;
4371 :
4372 : /**
4373 : * Hash of the associated order's contract terms.
4374 : */
4375 : struct TALER_PrivateContractHashP h_contract_terms;
4376 :
4377 : /**
4378 : * Public key of the coin that has been deposited.
4379 : */
4380 : struct TALER_CoinSpendPublicKeyP coin_pub;
4381 :
4382 : /**
4383 : * Signature of the coin that has been deposited.
4384 : */
4385 : struct TALER_CoinSpendSignatureP coin_sig;
4386 :
4387 : /**
4388 : * URL of the exchange.
4389 : */
4390 : const char *exchange_url;
4391 :
4392 : /**
4393 : * Value of the coin with fees applied.
4394 : */
4395 : struct TALER_Amount amount_with_fee;
4396 :
4397 : /**
4398 : * Fee charged for deposit.
4399 : */
4400 : struct TALER_Amount deposit_fee;
4401 :
4402 : /**
4403 : * Fee to be charged in case of a refund.
4404 : */
4405 : struct TALER_Amount refund_fee;
4406 :
4407 : /**
4408 : * Fee charged after the money is wired.
4409 : */
4410 : struct TALER_Amount wire_fee;
4411 :
4412 : /**
4413 : * Hash of the wire details.
4414 : */
4415 : struct TALER_MerchantWireHashP h_wire;
4416 :
4417 : /**
4418 : * Signature the exchange made on this deposit.
4419 : */
4420 : struct TALER_ExchangeSignatureP exchange_sig;
4421 :
4422 : };
4423 :
4424 :
4425 : /**
4426 : * Generates deposit data for an order.
4427 : *
4428 : * @param instance the instance to make the deposit to.
4429 : * @param account the merchant account to use.
4430 : * @param order the order this deposit is for.
4431 : * @param signkey the signing key to use.
4432 : * @param deposit the deposit data to fill.
4433 : */
4434 : static void
4435 78 : make_deposit (const struct InstanceData *instance,
4436 : const struct TALER_MERCHANTDB_AccountDetails *account,
4437 : const struct OrderData *order,
4438 : const struct ExchangeSignkeyData *signkey,
4439 : struct DepositData *deposit)
4440 : {
4441 : struct TALER_CoinSpendPrivateKeyP coin_priv;
4442 : struct GNUNET_TIME_Timestamp now;
4443 : struct TALER_Amount amount_without_fee;
4444 :
4445 78 : now = GNUNET_TIME_timestamp_get ();
4446 78 : deposit->timestamp = now;
4447 78 : GNUNET_assert (GNUNET_OK ==
4448 : TALER_JSON_contract_hash (order->contract,
4449 : &deposit->h_contract_terms));
4450 78 : GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv);
4451 78 : GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
4452 : &deposit->coin_pub.eddsa_pub);
4453 78 : deposit->exchange_url = "https://test-exchange/";
4454 78 : GNUNET_assert (GNUNET_OK ==
4455 : TALER_string_to_amount ("EUR:50.00",
4456 : &deposit->amount_with_fee));
4457 78 : GNUNET_assert (GNUNET_OK ==
4458 : TALER_string_to_amount ("EUR:1.00",
4459 : &deposit->deposit_fee));
4460 78 : GNUNET_assert (GNUNET_OK ==
4461 : TALER_string_to_amount ("EUR:1.50",
4462 : &deposit->refund_fee));
4463 78 : GNUNET_assert (GNUNET_OK ==
4464 : TALER_string_to_amount ("EUR:2.00",
4465 : &deposit->wire_fee));
4466 78 : GNUNET_assert (0 <=
4467 : TALER_amount_subtract (&amount_without_fee,
4468 : &deposit->amount_with_fee,
4469 : &deposit->deposit_fee));
4470 78 : deposit->h_wire = account->h_wire;
4471 78 : GNUNET_CRYPTO_random_block (&deposit->exchange_sig,
4472 : sizeof (deposit->exchange_sig));
4473 78 : GNUNET_CRYPTO_random_block (&deposit->coin_sig,
4474 : sizeof (deposit->coin_sig));
4475 78 : }
4476 :
4477 :
4478 : /**
4479 : * Tests inserting an exchange signing key into the database.
4480 : *
4481 : * @param signkey the signing key to insert.
4482 : * @param expected_result the result we expect the database to return.
4483 : * @return 0 on success, 1 otherwise.
4484 : */
4485 : static int
4486 5 : test_insert_exchange_signkey (const struct ExchangeSignkeyData *signkey,
4487 : enum GNUNET_DB_QueryStatus expected_result)
4488 : {
4489 5 : TEST_COND_RET_ON_FAIL (expected_result ==
4490 : TALER_MERCHANTDB_insert_exchange_signing_key (pg,
4491 : &signkey->master_pub,
4492 : &signkey->exchange_pub
4493 : ,
4494 : signkey->start_date,
4495 : signkey->expire_date,
4496 : signkey->end_date,
4497 : &signkey->master_sig),
4498 : "Insert exchange signkey failed\n");
4499 5 : return 0;
4500 : }
4501 :
4502 :
4503 : /**
4504 : * Tests inserting a deposit into the database.
4505 : *
4506 : * @param instance the instance the deposit was made to.
4507 : * @param signkey the signing key used.
4508 : * @param deposit the deposit information to insert.
4509 : * @param expected_result the result we expect the database to return.
4510 : * @return 0 on success, 1 otherwise.
4511 : */
4512 : static int
4513 31 : test_insert_deposit (const struct InstanceData *instance,
4514 : const struct ExchangeSignkeyData *signkey,
4515 : const struct DepositData *deposit,
4516 : enum GNUNET_DB_QueryStatus expected_result)
4517 : {
4518 : uint64_t row;
4519 : struct TALER_Amount awf;
4520 :
4521 31 : GNUNET_assert (0 <=
4522 : TALER_amount_subtract (&awf,
4523 : &deposit->amount_with_fee,
4524 : &deposit->deposit_fee));
4525 31 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
4526 31 : TEST_COND_RET_ON_FAIL (
4527 : TALER_MERCHANTDB_DCS_SUCCESS ==
4528 : TALER_MERCHANTDB_insert_deposit_confirmation (pg,
4529 : instance->instance.id,
4530 : deposit->timestamp,
4531 : &deposit->h_contract_terms,
4532 : deposit->exchange_url,
4533 : deposit->timestamp,
4534 : &awf,
4535 : &deposit->wire_fee,
4536 : &deposit->h_wire,
4537 : &deposit->exchange_sig,
4538 : &signkey->exchange_pub,
4539 : &row),
4540 : "Insert deposit confirmation failed\n");
4541 31 : TEST_COND_RET_ON_FAIL (
4542 : expected_result ==
4543 : TALER_MERCHANTDB_insert_deposit (pg,
4544 : 0,
4545 : row,
4546 : &deposit->coin_pub,
4547 : &deposit->coin_sig,
4548 : &deposit->amount_with_fee,
4549 : &deposit->deposit_fee,
4550 : &deposit->refund_fee,
4551 : GNUNET_TIME_absolute_get ()),
4552 : "Insert deposit failed\n");
4553 31 : return 0;
4554 : }
4555 :
4556 :
4557 : /**
4558 : * Tests that storing a deposit confirmation returns a status that
4559 : * identifies the *specific* reason why it could not be stored.
4560 : * Collapsing all of them into #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
4561 : * (as we used to) makes the pay handler commit a batch deposit
4562 : * without any deposit records, silently losing the money.
4563 : *
4564 : * @param instance the instance the deposit was made to.
4565 : * @param deposit the deposit to derive the (valid) defaults from.
4566 : * @param h_contract_terms contract to claim the deposit is for.
4567 : * @param h_wire target account to claim the deposit went to.
4568 : * @param exchange_pub exchange signing key to use.
4569 : * @param wire_fee wire fee to claim was charged.
4570 : * @param expected_status status the database should return.
4571 : * @return 0 on success, 1 otherwise.
4572 : */
4573 : static int
4574 4 : test_insert_deposit_confirmation_status (
4575 : const struct InstanceData *instance,
4576 : const struct DepositData *deposit,
4577 : const struct TALER_PrivateContractHashP *h_contract_terms,
4578 : const struct TALER_MerchantWireHashP *h_wire,
4579 : const struct TALER_ExchangePublicKeyP *exchange_pub,
4580 : const struct TALER_Amount *wire_fee,
4581 : enum TALER_MERCHANTDB_DepositConfirmationStatus expected_status)
4582 : {
4583 4 : uint64_t row = 0;
4584 : struct TALER_Amount awf;
4585 :
4586 4 : GNUNET_assert (0 <=
4587 : TALER_amount_subtract (&awf,
4588 : &deposit->amount_with_fee,
4589 : &deposit->deposit_fee));
4590 4 : TEST_SET_INSTANCE (instance->instance.id,
4591 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
4592 4 : TEST_COND_RET_ON_FAIL (
4593 : expected_status ==
4594 : TALER_MERCHANTDB_insert_deposit_confirmation (pg,
4595 : instance->instance.id,
4596 : deposit->timestamp,
4597 : h_contract_terms,
4598 : deposit->exchange_url,
4599 : deposit->timestamp,
4600 : &awf,
4601 : wire_fee,
4602 : h_wire,
4603 : &deposit->exchange_sig,
4604 : exchange_pub,
4605 : &row),
4606 : "Insert deposit confirmation returned the wrong status\n");
4607 4 : return 0;
4608 : }
4609 :
4610 :
4611 : /**
4612 : * Closure for testing deposit lookup
4613 : */
4614 : struct TestLookupDeposits_Closure
4615 : {
4616 : /**
4617 : * Number of deposits to compare to
4618 : */
4619 : unsigned int deposits_to_cmp_length;
4620 :
4621 : /**
4622 : * Pointer to array of deposit data
4623 : */
4624 : const struct DepositData *deposits_to_cmp;
4625 :
4626 : /**
4627 : * Pointer to array of number of matches per deposit
4628 : */
4629 : unsigned int *results_matching;
4630 :
4631 : /**
4632 : * Total number of results returned
4633 : */
4634 : unsigned int results_length;
4635 : };
4636 :
4637 :
4638 : /**
4639 : * Called after 'test_lookup_deposits'.
4640 : *
4641 : * @param cls pointer to the test lookup closure.
4642 : * @param coin_pub public key of the coin deposited.
4643 : * @param amount_with_fee amount of the deposit with fees.
4644 : * @param deposit_fee fee charged for the deposit.
4645 : * @param refund_fee fee charged in case of a refund.
4646 : * @param wire_fee wire transfer fee to be paid
4647 : */
4648 : static void
4649 4 : lookup_deposits_cb (void *cls,
4650 : const char *exchange_url,
4651 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
4652 : const struct TALER_Amount *amount_with_fee,
4653 : const struct TALER_Amount *deposit_fee,
4654 : const struct TALER_Amount *refund_fee,
4655 : const struct TALER_Amount *wire_fee)
4656 : {
4657 4 : struct TestLookupDeposits_Closure *cmp = cls;
4658 :
4659 4 : if (NULL == cmp)
4660 0 : return;
4661 4 : cmp->results_length += 1;
4662 10 : for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i)
4663 : {
4664 6 : if ((GNUNET_OK ==
4665 6 : TALER_amount_cmp_currency (
4666 6 : &cmp->deposits_to_cmp[i].amount_with_fee,
4667 6 : amount_with_fee)) &&
4668 : (0 ==
4669 6 : TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee,
4670 4 : amount_with_fee)) &&
4671 : (GNUNET_OK ==
4672 4 : TALER_amount_cmp_currency (
4673 4 : &cmp->deposits_to_cmp[i].deposit_fee,
4674 4 : deposit_fee)) &&
4675 : (0 ==
4676 4 : TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee,
4677 4 : deposit_fee)) &&
4678 : (GNUNET_OK ==
4679 4 : TALER_amount_cmp_currency (
4680 4 : &cmp->deposits_to_cmp[i].refund_fee,
4681 4 : refund_fee)) &&
4682 : (0 ==
4683 4 : TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee,
4684 : refund_fee)))
4685 : {
4686 4 : cmp->results_matching[i] += 1;
4687 : }
4688 :
4689 : }
4690 : }
4691 :
4692 :
4693 : /**
4694 : * Tests looking up deposits from the database.
4695 : *
4696 : * @param instance the instance to lookup deposits from.
4697 : * @param h_contract_terms the contract terms that the deposits should have.
4698 : * @param deposits_length length of @e deposits.
4699 : * @param deposits the deposits we expect to be found.
4700 : * @return 0 on success, 1 otherwise.
4701 : */
4702 : static int
4703 4 : test_lookup_deposits (const struct InstanceData *instance,
4704 : const struct TALER_PrivateContractHashP *h_contract_terms,
4705 : unsigned int deposits_length,
4706 : const struct DepositData *deposits)
4707 4 : {
4708 4 : unsigned int results_matching[GNUNET_NZL (deposits_length)];
4709 4 : struct TestLookupDeposits_Closure cmp = {
4710 : .deposits_to_cmp_length = deposits_length,
4711 : .deposits_to_cmp = deposits,
4712 : .results_matching = results_matching,
4713 : .results_length = 0
4714 : };
4715 4 : memset (results_matching,
4716 : 0,
4717 : sizeof (unsigned int) * deposits_length);
4718 4 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
4719 4 : TEST_COND_RET_ON_FAIL (0 <=
4720 : TALER_MERCHANTDB_iterate_deposits (pg,
4721 : instance->instance.id,
4722 : h_contract_terms,
4723 : &lookup_deposits_cb,
4724 : &cmp),
4725 : "Lookup deposits failed\n");
4726 4 : if (deposits_length != cmp.results_length)
4727 : {
4728 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4729 : "Lookup deposits failed: incorrect number of results returned (%d)\n",
4730 : cmp.results_length);
4731 0 : return 1;
4732 : }
4733 8 : for (unsigned int i = 0; deposits_length > i; ++i)
4734 : {
4735 4 : if (cmp.results_matching[i] != 1)
4736 : {
4737 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4738 : "Lookup deposits failed: mismatched data\n");
4739 0 : return 1;
4740 : }
4741 : }
4742 4 : return 0;
4743 : }
4744 :
4745 :
4746 : /**
4747 : * Called after 'test_lookup_deposits_contract_and_coin'.
4748 : *
4749 : * @param cls pointer to the test lookup closure.
4750 : * @param exchange_url URL to the exchange
4751 : * @param amount_with_fee amount of the deposit with fees.
4752 : * @param deposit_fee fee charged for the deposit.
4753 : * @param refund_fee fee charged in case of a refund.
4754 : * @param wire_fee fee charged when the money is wired.
4755 : * @param h_wire hash of the wire transfer details.
4756 : * @param deposit_timestamp when the deposit was made.
4757 : * @param refund_deadline deadline for refunding the deposit.
4758 : * @param exchange_sig signature the exchange made on the deposit.
4759 : * @param exchange_pub public key of the exchange.
4760 : */
4761 : static void
4762 1 : lookup_deposits_contract_coin_cb (
4763 : void *cls,
4764 : const char *exchange_url,
4765 : const struct TALER_Amount *amount_with_fee,
4766 : const struct TALER_Amount *deposit_fee,
4767 : const struct TALER_Amount *refund_fee,
4768 : const struct TALER_Amount *wire_fee,
4769 : const struct TALER_MerchantWireHashP *h_wire,
4770 : struct GNUNET_TIME_Timestamp deposit_timestamp,
4771 : struct GNUNET_TIME_Timestamp refund_deadline,
4772 : const struct TALER_ExchangeSignatureP *exchange_sig,
4773 : const struct TALER_ExchangePublicKeyP *exchange_pub)
4774 : {
4775 1 : struct TestLookupDeposits_Closure *cmp = cls;
4776 :
4777 1 : if (NULL == cmp)
4778 0 : return;
4779 1 : cmp->results_length++;
4780 2 : for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i)
4781 : {
4782 1 : if ((GNUNET_TIME_timestamp_cmp (cmp->deposits_to_cmp[i].timestamp,
4783 : ==,
4784 1 : deposit_timestamp)) &&
4785 1 : (0 == strcmp (cmp->deposits_to_cmp[i].exchange_url,
4786 1 : exchange_url)) &&
4787 1 : (GNUNET_OK == TALER_amount_cmp_currency (
4788 1 : &cmp->deposits_to_cmp[i].amount_with_fee,
4789 1 : amount_with_fee)) &&
4790 1 : (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee,
4791 1 : amount_with_fee)) &&
4792 1 : (GNUNET_OK == TALER_amount_cmp_currency (
4793 1 : &cmp->deposits_to_cmp[i].deposit_fee,
4794 1 : deposit_fee)) &&
4795 1 : (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee,
4796 1 : deposit_fee)) &&
4797 1 : (GNUNET_OK == TALER_amount_cmp_currency (
4798 1 : &cmp->deposits_to_cmp[i].refund_fee,
4799 1 : refund_fee)) &&
4800 1 : (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee,
4801 1 : refund_fee)) &&
4802 1 : (GNUNET_OK == TALER_amount_cmp_currency (
4803 1 : &cmp->deposits_to_cmp[i].wire_fee,
4804 1 : wire_fee)) &&
4805 1 : (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].wire_fee,
4806 1 : wire_fee)) &&
4807 1 : (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire,
4808 1 : h_wire)) &&
4809 1 : (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].exchange_sig,
4810 : exchange_sig)))
4811 : {
4812 1 : cmp->results_matching[i]++;
4813 : }
4814 : }
4815 : }
4816 :
4817 :
4818 : /**
4819 : * Tests lookup of deposits by contract and coin.
4820 : *
4821 : * @param instance the instance to lookup from.
4822 : * @param h_contract the contract terms the deposits should have.
4823 : * @param coin_pub the public key of the coin the deposits should have.
4824 : * @param deposits_length length of @e deposits.
4825 : * @param deposits the deposits the db is expected to find.
4826 : * @return 0 on success, 1 otherwise.
4827 : */
4828 : static int
4829 1 : test_lookup_deposits_contract_and_coin (
4830 : const struct InstanceData *instance,
4831 : const struct TALER_PrivateContractHashP *h_contract,
4832 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
4833 : unsigned int deposits_length,
4834 : const struct DepositData *deposits)
4835 1 : {
4836 1 : unsigned int results_matching[deposits_length];
4837 1 : struct TestLookupDeposits_Closure cmp = {
4838 : .deposits_to_cmp_length = deposits_length,
4839 : .deposits_to_cmp = deposits,
4840 : .results_matching = results_matching,
4841 : .results_length = 0
4842 : };
4843 1 : memset (results_matching,
4844 : 0,
4845 : sizeof (unsigned int) * deposits_length);
4846 1 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
4847 1 : TEST_COND_RET_ON_FAIL (
4848 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
4849 : TALER_MERCHANTDB_iterate_deposits_by_contract_and_coin (
4850 : pg,
4851 : instance->instance.id,
4852 : h_contract,
4853 : coin_pub,
4854 : &lookup_deposits_contract_coin_cb,
4855 : &cmp),
4856 : "Lookup deposits by contract and coin failed\n");
4857 1 : if (deposits_length != cmp.results_length)
4858 : {
4859 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4860 : "Lookup deposits failed: incorrect number of results returned\n");
4861 0 : return 1;
4862 : }
4863 2 : for (unsigned int i = 0; deposits_length > i; ++i)
4864 : {
4865 1 : if (cmp.results_matching[i] != 1)
4866 : {
4867 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4868 : "Lookup deposits failed: mismatched data\n");
4869 0 : return 1;
4870 : }
4871 : }
4872 1 : return 0;
4873 : }
4874 :
4875 :
4876 : /**
4877 : * Called after 'test_lookup_deposits_by_order'.
4878 : *
4879 : * @param cls pointer to the test lookup closure.
4880 : * @param deposit_serial row number of the deposit in the database.
4881 : * @param exchange_url URL to the exchange
4882 : * @param h_wire hash of the wire transfer details.
4883 : * @param deposit_timestamp when was the deposit made
4884 : * @param amount_with_fee amount of the deposit with fees.
4885 : * @param deposit_fee fee charged for the deposit.
4886 : * @param coin_pub public key of the coin deposited.
4887 : */
4888 : static void
4889 3 : lookup_deposits_order_cb (void *cls,
4890 : uint64_t deposit_serial,
4891 : const char *exchange_url,
4892 : const struct TALER_MerchantWireHashP *h_wire,
4893 : struct GNUNET_TIME_Timestamp deposit_timestamp,
4894 : const struct TALER_Amount *amount_with_fee,
4895 : const struct TALER_Amount *deposit_fee,
4896 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
4897 : {
4898 3 : struct TestLookupDeposits_Closure *cmp = cls;
4899 :
4900 3 : if (NULL == cmp)
4901 0 : return;
4902 3 : cmp->results_length += 1;
4903 8 : for (unsigned int i = 0; i < cmp->deposits_to_cmp_length; ++i)
4904 : {
4905 5 : if ((0 == strcmp (cmp->deposits_to_cmp[i].exchange_url,
4906 5 : exchange_url)) &&
4907 5 : (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire,
4908 5 : h_wire)) &&
4909 5 : (GNUNET_OK == TALER_amount_cmp_currency (
4910 5 : &cmp->deposits_to_cmp[i].amount_with_fee,
4911 5 : amount_with_fee)) &&
4912 5 : (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee,
4913 3 : amount_with_fee)) &&
4914 3 : (GNUNET_OK == TALER_amount_cmp_currency (
4915 3 : &cmp->deposits_to_cmp[i].deposit_fee,
4916 3 : deposit_fee)) &&
4917 3 : (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee,
4918 3 : deposit_fee)) &&
4919 3 : (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].coin_pub,
4920 : coin_pub)))
4921 3 : cmp->results_matching[i] += 1;
4922 : }
4923 : }
4924 :
4925 :
4926 : /**
4927 : * Tests looking up deposits by associated order.
4928 : *
4929 : * @param order_serial row number of the order to lookup for.
4930 : * @param deposits_length length of @e deposits_length.
4931 : * @param deposits the deposits we expect to be found.
4932 : * @return 0 on success, 1 otherwise.
4933 : */
4934 : static int
4935 2 : test_lookup_deposits_by_order (uint64_t order_serial,
4936 : unsigned int deposits_length,
4937 : const struct DepositData *deposits)
4938 2 : {
4939 2 : unsigned int results_matching[deposits_length];
4940 2 : struct TestLookupDeposits_Closure cmp = {
4941 : .deposits_to_cmp_length = deposits_length,
4942 : .deposits_to_cmp = deposits,
4943 : .results_matching = results_matching,
4944 : .results_length = 0
4945 : };
4946 2 : memset (results_matching,
4947 : 0,
4948 : sizeof (unsigned int) * deposits_length);
4949 2 : if (deposits_length !=
4950 2 : TALER_MERCHANTDB_iterate_deposits_by_order (pg,
4951 : order_serial,
4952 : &lookup_deposits_order_cb,
4953 : &cmp))
4954 : {
4955 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4956 : "Lookup deposits by order failed\n");
4957 0 : return 1;
4958 : }
4959 2 : if (deposits_length != cmp.results_length)
4960 : {
4961 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4962 : "Lookup deposits by order failed: incorrect number of results\n");
4963 0 : return 1;
4964 : }
4965 5 : for (unsigned int i = 0; i < deposits_length; ++i)
4966 : {
4967 3 : if (1 != cmp.results_matching[i])
4968 : {
4969 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
4970 : "Lookup deposits by order failed: mismatched data\n");
4971 0 : return 1;
4972 : }
4973 : }
4974 2 : return 0;
4975 : }
4976 :
4977 :
4978 : /**
4979 : * Container for information for looking up the row number of a deposit.
4980 : */
4981 : struct LookupDepositSerial_Closure
4982 : {
4983 : /**
4984 : * The deposit we're looking for.
4985 : */
4986 : const struct DepositData *deposit;
4987 :
4988 : /**
4989 : * The serial found.
4990 : */
4991 : uint64_t serial;
4992 : };
4993 :
4994 :
4995 : /**
4996 : * Called after 'get_deposit_serial'.
4997 : *
4998 : * @param cls pointer to the test lookup closure.
4999 : * @param deposit_serial row number of the deposit in the database.
5000 : * @param exchange_url URL to the exchange
5001 : * @param h_wire hash of the wire transfer details.
5002 : * @param deposit_timestamp when was the deposit made.
5003 : * @param amount_with_fee amount of the deposit with fees.
5004 : * @param deposit_fee fee charged for the deposit.
5005 : * @param coin_pub public key of the coin deposited.
5006 : */
5007 : static void
5008 12 : get_deposit_serial_cb (void *cls,
5009 : uint64_t deposit_serial,
5010 : const char *exchange_url,
5011 : const struct TALER_MerchantWireHashP *h_wire,
5012 : struct GNUNET_TIME_Timestamp deposit_timestamp,
5013 : const struct TALER_Amount *amount_with_fee,
5014 : const struct TALER_Amount *deposit_fee,
5015 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
5016 : {
5017 12 : struct LookupDepositSerial_Closure *lookup_cls = cls;
5018 :
5019 : (void) deposit_timestamp;
5020 12 : if (NULL == lookup_cls)
5021 0 : return;
5022 12 : if ((0 == strcmp (lookup_cls->deposit->exchange_url,
5023 12 : exchange_url)) &&
5024 12 : (0 == GNUNET_memcmp (&lookup_cls->deposit->h_wire,
5025 12 : h_wire)) &&
5026 12 : (GNUNET_OK == TALER_amount_cmp_currency (
5027 12 : &lookup_cls->deposit->amount_with_fee,
5028 12 : amount_with_fee)) &&
5029 12 : (0 == TALER_amount_cmp (&lookup_cls->deposit->amount_with_fee,
5030 12 : amount_with_fee)) &&
5031 12 : (GNUNET_OK == TALER_amount_cmp_currency (
5032 12 : &lookup_cls->deposit->deposit_fee,
5033 12 : deposit_fee)) &&
5034 12 : (0 == TALER_amount_cmp (&lookup_cls->deposit->deposit_fee,
5035 12 : deposit_fee)) &&
5036 12 : (0 == GNUNET_memcmp (&lookup_cls->deposit->coin_pub,
5037 : coin_pub)))
5038 9 : lookup_cls->serial = deposit_serial;
5039 : }
5040 :
5041 :
5042 : /**
5043 : * Convenience function to retrieve the row number of a deposit in the database.
5044 : *
5045 : * @param instance the instance to get deposits from.
5046 : * @param order the order associated with the deposit.
5047 : * @param deposit the deposit to lookup the serial for.
5048 : * @return the row number of the deposit.
5049 : */
5050 : static uint64_t
5051 9 : get_deposit_serial (const struct InstanceData *instance,
5052 : const struct OrderData *order,
5053 : const struct DepositData *deposit)
5054 : {
5055 9 : uint64_t order_serial = get_order_serial (instance,
5056 : order);
5057 9 : struct LookupDepositSerial_Closure lookup_cls = {
5058 : .deposit = deposit,
5059 : .serial = 0
5060 : };
5061 :
5062 9 : GNUNET_assert (0 <
5063 : TALER_MERCHANTDB_iterate_deposits_by_order (pg,
5064 : order_serial,
5065 : &get_deposit_serial_cb,
5066 : &lookup_cls));
5067 9 : GNUNET_assert (0 != lookup_cls.serial);
5068 :
5069 9 : return lookup_cls.serial;
5070 : }
5071 :
5072 :
5073 : /**
5074 : * Closure for deposit tests.
5075 : */
5076 : struct TestDeposits_Closure
5077 : {
5078 : /**
5079 : * The instance settings
5080 : */
5081 : struct InstanceData instance;
5082 :
5083 : /**
5084 : * The merchant account
5085 : */
5086 : struct TALER_MERCHANTDB_AccountDetails account;
5087 :
5088 : /**
5089 : * The exchange signing key
5090 : */
5091 : struct ExchangeSignkeyData signkey;
5092 :
5093 : /**
5094 : * The order data
5095 : */
5096 : struct OrderData orders[2];
5097 :
5098 : /**
5099 : * The array of deposits
5100 : */
5101 : struct DepositData deposits[3];
5102 : };
5103 :
5104 :
5105 : /**
5106 : * Initializes data for testing deposits.
5107 : *
5108 : * @param cls the test closure to initialize.
5109 : */
5110 : static void
5111 1 : pre_test_deposits (struct TestDeposits_Closure *cls)
5112 : {
5113 : /* Instance */
5114 1 : make_instance ("test_inst_deposits",
5115 : &cls->instance);
5116 :
5117 : /* Account */
5118 1 : make_account (&cls->account);
5119 1 : cls->account.instance_id = cls->instance.instance.id;
5120 : /* Signing key */
5121 1 : make_exchange_signkey (&cls->signkey);
5122 :
5123 : /* Order */
5124 1 : make_order ("test_deposits_od_1",
5125 : &cls->orders[0]);
5126 1 : make_order ("test_deposits_od_2",
5127 : &cls->orders[1]);
5128 :
5129 : /* Deposit */
5130 1 : make_deposit (&cls->instance,
5131 1 : &cls->account,
5132 1 : &cls->orders[0],
5133 1 : &cls->signkey,
5134 : &cls->deposits[0]);
5135 1 : make_deposit (&cls->instance,
5136 1 : &cls->account,
5137 1 : &cls->orders[0],
5138 1 : &cls->signkey,
5139 : &cls->deposits[1]);
5140 1 : GNUNET_assert (GNUNET_OK ==
5141 : TALER_string_to_amount ("EUR:29.00",
5142 : &cls->deposits[1].amount_with_fee));
5143 1 : make_deposit (&cls->instance,
5144 1 : &cls->account,
5145 1 : &cls->orders[1],
5146 1 : &cls->signkey,
5147 : &cls->deposits[2]);
5148 1 : }
5149 :
5150 :
5151 : /**
5152 : * Cleans up memory after testing deposits.
5153 : *
5154 : * @param cls the closure containing memory to free.
5155 : */
5156 : static void
5157 1 : post_test_deposits (struct TestDeposits_Closure *cls)
5158 : {
5159 1 : free_instance_data (&cls->instance);
5160 1 : json_decref (cls->orders[0].contract);
5161 1 : json_decref (cls->orders[1].contract);
5162 1 : }
5163 :
5164 :
5165 : /**
5166 : * Counts the pending deposits we are called for.
5167 : *
5168 : * @param cls a `unsigned int *` counter
5169 : */
5170 : static void
5171 3 : count_pending_deposits_cb (
5172 : void *cls,
5173 : uint64_t deposit_serial,
5174 : struct GNUNET_TIME_Absolute wire_deadline,
5175 : struct GNUNET_TIME_Absolute retry_time,
5176 : const struct TALER_PrivateContractHashP *h_contract_terms,
5177 : const struct TALER_MerchantPrivateKeyP *merchant_priv,
5178 : const char *instance_id,
5179 : const struct TALER_MerchantWireHashP *h_wire,
5180 : const struct TALER_Amount *amount_with_fee,
5181 : const struct TALER_Amount *deposit_fee,
5182 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
5183 : {
5184 3 : unsigned int *count = cls;
5185 :
5186 : (void) deposit_serial;
5187 : (void) wire_deadline;
5188 : (void) retry_time;
5189 : (void) h_contract_terms;
5190 : (void) merchant_priv;
5191 : (void) instance_id;
5192 : (void) h_wire;
5193 : (void) amount_with_fee;
5194 : (void) deposit_fee;
5195 : (void) coin_pub;
5196 3 : (*count)++;
5197 3 : }
5198 :
5199 :
5200 : /**
5201 : * Tests that pending deposits of an instance whose private key was
5202 : * deleted do not break the iteration.
5203 : *
5204 : * Regression test: 'merchant_priv' is nullable and DELETE
5205 : * /management/instances/$ID sets it to NULL, but the result spec did
5206 : * not allow NULL. Extraction then failed with a hard error, which
5207 : * makes taler-merchant-depositcheck shut down merchant-wide and
5208 : * crash-loop on the very same row.
5209 : *
5210 : * @param instance the instance whose private key gets deleted.
5211 : * @param exchange_url the exchange the deposits were made to.
5212 : * @return 0 on success, 1 otherwise.
5213 : */
5214 : static int
5215 1 : test_pending_deposits_without_private_key (const struct InstanceData *instance,
5216 : const char *exchange_url)
5217 : {
5218 1 : unsigned int count = 0;
5219 :
5220 : /* Before the deletion the deposits are pending. */
5221 1 : TEST_COND_RET_ON_FAIL (0 <
5222 : TALER_MERCHANTDB_iterate_pending_deposits (
5223 : pg,
5224 : exchange_url,
5225 : 1024,
5226 : true,
5227 : &count_pending_deposits_cb,
5228 : &count),
5229 : "Iterating over pending deposits failed\n");
5230 1 : TEST_COND_RET_ON_FAIL (0 < count,
5231 : "No pending deposits found\n");
5232 1 : TEST_RET_ON_FAIL (test_delete_instance_private_key (
5233 : instance,
5234 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5235 : /* Now they are unsettleable, but that must not be an error. */
5236 1 : count = 0;
5237 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
5238 : TALER_MERCHANTDB_iterate_pending_deposits (
5239 : pg,
5240 : exchange_url,
5241 : 1024,
5242 : true,
5243 : &count_pending_deposits_cb,
5244 : &count),
5245 : "Pending deposits of an instance without a private key"
5246 : " were not skipped\n");
5247 1 : TEST_COND_RET_ON_FAIL (0 == count,
5248 : "Deposit of an instance without a private key"
5249 : " was returned\n");
5250 1 : return 0;
5251 : }
5252 :
5253 :
5254 : /**
5255 : * Runs tests for deposits.
5256 : *
5257 : * @param cls the closure containing test data.
5258 : * @return 0 on success, 1 otherwise.
5259 : */
5260 : static int
5261 1 : run_test_deposits (struct TestDeposits_Closure *cls)
5262 : {
5263 : /* Insert the instance */
5264 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
5265 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5266 : /* Insert an account */
5267 1 : TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
5268 : &cls->account,
5269 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5270 : /* Insert a signing key */
5271 1 : TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
5272 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5273 1 : TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
5274 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
5275 : /* Insert an order */
5276 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
5277 : &cls->orders[0],
5278 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5279 : /* Insert contract terms */
5280 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
5281 : &cls->orders[0],
5282 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5283 : /* Test inserting a deposit */
5284 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
5285 : &cls->signkey,
5286 : &cls->deposits[0],
5287 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5288 : /* Test double inserts are idempotent */
5289 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
5290 : &cls->signkey,
5291 : &cls->deposits[0],
5292 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
5293 : /* Each reason for not storing a deposit confirmation must be
5294 : reported distinctly, and none of them may look like success. */
5295 : {
5296 : struct TALER_PrivateContractHashP bogus_h_contract;
5297 : struct TALER_MerchantWireHashP bogus_h_wire;
5298 : struct TALER_ExchangePublicKeyP bogus_exchange_pub;
5299 : struct TALER_Amount other_wire_fee;
5300 :
5301 1 : GNUNET_CRYPTO_random_block (&bogus_h_contract,
5302 : sizeof (bogus_h_contract));
5303 1 : GNUNET_CRYPTO_random_block (&bogus_h_wire,
5304 : sizeof (bogus_h_wire));
5305 1 : GNUNET_CRYPTO_random_block (&bogus_exchange_pub,
5306 : sizeof (bogus_exchange_pub));
5307 1 : GNUNET_assert (GNUNET_OK ==
5308 : TALER_string_to_amount ("EUR:3.00",
5309 : &other_wire_fee));
5310 : /* unknown merchant account */
5311 1 : TEST_RET_ON_FAIL (test_insert_deposit_confirmation_status (
5312 : &cls->instance,
5313 : &cls->deposits[0],
5314 : &cls->deposits[0].h_contract_terms,
5315 : &bogus_h_wire,
5316 : &cls->signkey.exchange_pub,
5317 : &cls->deposits[0].wire_fee,
5318 : TALER_MERCHANTDB_DCS_NO_ACCOUNT));
5319 : /* unknown exchange signing key */
5320 1 : TEST_RET_ON_FAIL (test_insert_deposit_confirmation_status (
5321 : &cls->instance,
5322 : &cls->deposits[0],
5323 : &cls->deposits[0].h_contract_terms,
5324 : &cls->deposits[0].h_wire,
5325 : &bogus_exchange_pub,
5326 : &cls->deposits[0].wire_fee,
5327 : TALER_MERCHANTDB_DCS_NO_SIGNKEY));
5328 : /* unknown order */
5329 1 : TEST_RET_ON_FAIL (test_insert_deposit_confirmation_status (
5330 : &cls->instance,
5331 : &cls->deposits[0],
5332 : &bogus_h_contract,
5333 : &cls->deposits[0].h_wire,
5334 : &cls->signkey.exchange_pub,
5335 : &cls->deposits[0].wire_fee,
5336 : TALER_MERCHANTDB_DCS_NO_ORDER));
5337 : /* conflicting deposit confirmation (different wire fee) */
5338 1 : TEST_RET_ON_FAIL (test_insert_deposit_confirmation_status (
5339 : &cls->instance,
5340 : &cls->deposits[0],
5341 : &cls->deposits[0].h_contract_terms,
5342 : &cls->deposits[0].h_wire,
5343 : &cls->signkey.exchange_pub,
5344 : &other_wire_fee,
5345 : TALER_MERCHANTDB_DCS_CONFLICT));
5346 : }
5347 : /* Test lookup deposits */
5348 1 : TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
5349 : &cls->deposits[0].h_contract_terms,
5350 : 1,
5351 : cls->deposits));
5352 1 : TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
5353 : &cls->deposits[2].h_contract_terms,
5354 : 0,
5355 : NULL));
5356 : /* Test lookup deposits by contract and coins */
5357 1 : TEST_RET_ON_FAIL (test_lookup_deposits_contract_and_coin (
5358 : &cls->instance,
5359 : &cls->deposits[0].h_contract_terms,
5360 : &cls->deposits[0].coin_pub,
5361 : 1,
5362 : cls->deposits));
5363 : /* Test multiple deposits */
5364 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
5365 : &cls->signkey,
5366 : &cls->deposits[1],
5367 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5368 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
5369 : &cls->orders[1],
5370 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5371 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
5372 : &cls->orders[1],
5373 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5374 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
5375 : &cls->signkey,
5376 : &cls->deposits[2],
5377 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
5378 1 : TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
5379 : &cls->deposits[0].h_contract_terms,
5380 : 2,
5381 : cls->deposits));
5382 1 : TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
5383 : &cls->deposits[2].h_contract_terms,
5384 : 1,
5385 : &cls->deposits[2]));
5386 : /* Test lookup deposits by order */
5387 : {
5388 1 : uint64_t order_serial = get_order_serial (&cls->instance,
5389 1 : &cls->orders[0]);
5390 1 : TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial,
5391 : 2,
5392 : cls->deposits));
5393 1 : order_serial = get_order_serial (&cls->instance,
5394 1 : &cls->orders[1]);
5395 1 : TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial,
5396 : 1,
5397 : &cls->deposits[2]));
5398 : }
5399 : /* Must be last: deletes the private key of the instance. */
5400 1 : TEST_RET_ON_FAIL (test_pending_deposits_without_private_key (
5401 : &cls->instance,
5402 : cls->deposits[0].exchange_url));
5403 1 : return 0;
5404 : }
5405 :
5406 :
5407 : /**
5408 : * Handles functionality for testing deposits.
5409 : *
5410 : * @return 0 on success, 1 otherwise.
5411 : */
5412 : static int
5413 1 : test_deposits (void)
5414 : {
5415 : struct TestDeposits_Closure test_cls;
5416 : int test_result;
5417 :
5418 1 : pre_test_deposits (&test_cls);
5419 1 : test_result = run_test_deposits (&test_cls);
5420 1 : post_test_deposits (&test_cls);
5421 1 : return test_result;
5422 : }
5423 :
5424 :
5425 : /* *********** Transfers ********** */
5426 :
5427 :
5428 : /**
5429 : * Container for wire fee data for an exchange.
5430 : */
5431 : struct WireFeeData
5432 : {
5433 : /**
5434 : * The method used.
5435 : */
5436 : const char *wire_method;
5437 :
5438 : /**
5439 : * Hash of the wire method.
5440 : */
5441 : struct GNUNET_HashCode h_wire_method;
5442 :
5443 : /**
5444 : * Wire fees charged.
5445 : */
5446 : struct TALER_WireFeeSet fees;
5447 :
5448 : /**
5449 : * Start date of the wire fee.
5450 : */
5451 : struct GNUNET_TIME_Timestamp wire_fee_start;
5452 :
5453 : /**
5454 : * End date of the wire fee.
5455 : */
5456 : struct GNUNET_TIME_Timestamp wire_fee_end;
5457 :
5458 : /**
5459 : * Signature on the wire fee.
5460 : */
5461 : struct TALER_MasterSignatureP fee_sig;
5462 : };
5463 :
5464 :
5465 : /**
5466 : * Creates data for an exchange wire fee.
5467 : *
5468 : * @param signkey the exchange signing key data.
5469 : * @param wire_fee where to store the wire fee data.
5470 : */
5471 : static void
5472 2 : make_wire_fee (const struct ExchangeSignkeyData *signkey,
5473 : struct WireFeeData *wire_fee)
5474 : {
5475 2 : wire_fee->wire_method = "wire-method";
5476 2 : GNUNET_CRYPTO_hash (wire_fee->wire_method,
5477 2 : strlen (wire_fee->wire_method) + 1,
5478 : &wire_fee->h_wire_method);
5479 2 : GNUNET_assert (GNUNET_OK ==
5480 : TALER_string_to_amount ("EUR:0.49",
5481 : &wire_fee->fees.wire));
5482 2 : GNUNET_assert (GNUNET_OK ==
5483 : TALER_string_to_amount ("EUR:0.49",
5484 : &wire_fee->fees.closing));
5485 2 : wire_fee->wire_fee_start = GNUNET_TIME_timestamp_get ();
5486 2 : wire_fee->wire_fee_end = GNUNET_TIME_relative_to_timestamp (
5487 : GNUNET_TIME_UNIT_MONTHS);
5488 2 : TALER_exchange_offline_wire_fee_sign (
5489 : wire_fee->wire_method,
5490 : wire_fee->wire_fee_start,
5491 : wire_fee->wire_fee_end,
5492 2 : &wire_fee->fees,
5493 : &signkey->master_priv,
5494 : &wire_fee->fee_sig);
5495 2 : }
5496 :
5497 :
5498 : /**
5499 : * Container for wire transfer data.
5500 : */
5501 : struct TransferData
5502 : {
5503 : /**
5504 : * Id of the transfer.
5505 : */
5506 : struct TALER_WireTransferIdentifierRawP wtid;
5507 :
5508 : /**
5509 : * The main data for the transfer.
5510 : */
5511 : struct TALER_EXCHANGE_TransferData data;
5512 :
5513 : /**
5514 : * URL to the exchange the transfer was made through.
5515 : */
5516 : const char *exchange_url;
5517 :
5518 : /**
5519 : * How much the fee for the deposit was.
5520 : */
5521 : struct TALER_Amount deposit_fee;
5522 :
5523 : /**
5524 : * Whether the transfer has been confirmed.
5525 : */
5526 : bool confirmed;
5527 :
5528 : /**
5529 : * Whether the transfer has been verified.
5530 : */
5531 : bool verified;
5532 : };
5533 :
5534 :
5535 : /**
5536 : * Creates a transfer for use with testing.
5537 : *
5538 : * @param deposits_length length of @e deposits.
5539 : * @param deposits list of deposits to combine into one transfer.
5540 : * @param transfer where to write the transfer data.
5541 : */
5542 : static void
5543 5 : make_transfer (const struct ExchangeSignkeyData *signkey,
5544 : unsigned int deposits_length,
5545 : const struct DepositData deposits[static deposits_length],
5546 : struct TransferData *transfer)
5547 5 : {
5548 5 : struct TALER_TrackTransferDetails *details = NULL;
5549 :
5550 5 : GNUNET_CRYPTO_random_block (&transfer->wtid,
5551 : sizeof (struct TALER_WireTransferIdentifierRawP));
5552 5 : transfer->exchange_url = deposits[0].exchange_url;
5553 5 : transfer->verified = false;
5554 5 : transfer->confirmed = false;
5555 5 : transfer->data.details_length = 0;
5556 5 : GNUNET_assert (GNUNET_OK ==
5557 : TALER_amount_set_zero (deposits[0].amount_with_fee.currency,
5558 : &transfer->data.total_amount));
5559 5 : GNUNET_assert (GNUNET_OK ==
5560 : TALER_amount_set_zero (deposits[0].amount_with_fee.currency,
5561 : &transfer->deposit_fee));
5562 11 : for (unsigned int i = 0; i < deposits_length; ++i)
5563 : {
5564 6 : GNUNET_array_grow (details,
5565 : transfer->data.details_length,
5566 : i + 1);
5567 6 : details[i].h_contract_terms = deposits[i].h_contract_terms;
5568 6 : details[i].coin_pub = deposits[i].coin_pub;
5569 6 : details[i].coin_value = deposits[i].amount_with_fee;
5570 6 : details[i].coin_fee = deposits[i].deposit_fee;
5571 6 : GNUNET_assert (0 <=
5572 : TALER_amount_add (&transfer->data.total_amount,
5573 : &transfer->data.total_amount,
5574 : &deposits[i].amount_with_fee));
5575 6 : GNUNET_assert (0 <=
5576 : TALER_amount_add (&transfer->deposit_fee,
5577 : &transfer->deposit_fee,
5578 : &deposits[i].deposit_fee));
5579 : }
5580 5 : transfer->data.exchange_pub = signkey->exchange_pub;
5581 5 : transfer->data.execution_time = GNUNET_TIME_timestamp_get ();
5582 5 : transfer->data.details = details;
5583 5 : GNUNET_assert (GNUNET_OK ==
5584 : TALER_string_to_amount ("EUR:0.50",
5585 : &transfer->data.wire_fee));
5586 5 : }
5587 :
5588 :
5589 : /**
5590 : * Closure for testing 'iterate_transfer_summaries'
5591 : */
5592 : struct TestLookupTransferSummary_Closure
5593 : {
5594 : /**
5595 : * Id of the order the transfer was made for.
5596 : */
5597 : const char *order_id;
5598 :
5599 : /**
5600 : * The value of the deposit made.
5601 : */
5602 : const struct TALER_Amount *deposit_value;
5603 :
5604 : /**
5605 : * The fee on the deposit made.
5606 : */
5607 : const struct TALER_Amount *deposit_fee;
5608 :
5609 : /**
5610 : * 0 if the comparison is true, 1 if false.
5611 : */
5612 : int result;
5613 : };
5614 :
5615 :
5616 : /**
5617 : * Called after 'test_lookup_transfer_summary'.
5618 : *
5619 : * @param cls pointer to 'TestLookupTransferSummary_Closure'.
5620 : * @param order_id id of the order the transfer was made for.
5621 : * @param deposit_value the value of the deposit made.
5622 : * @param deposit_fee the fee on the deposit made.
5623 : */
5624 : static void
5625 1 : lookup_transfer_summary_cb (void *cls,
5626 : const char *order_id,
5627 : const struct TALER_Amount *deposit_value,
5628 : const struct TALER_Amount *deposit_fee)
5629 : {
5630 1 : struct TestLookupTransferSummary_Closure *cmp = cls;
5631 1 : if (NULL == cmp)
5632 0 : return;
5633 1 : if ((0 == strcmp (cmp->order_id,
5634 1 : order_id)) &&
5635 1 : (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_value,
5636 1 : deposit_value)) &&
5637 1 : (0 == TALER_amount_cmp (cmp->deposit_value,
5638 1 : deposit_value)) &&
5639 1 : (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_fee,
5640 1 : deposit_fee)) &&
5641 1 : (0 == TALER_amount_cmp (cmp->deposit_fee,
5642 : deposit_fee)))
5643 1 : cmp->result = 0;
5644 : else
5645 0 : cmp->result = 1;
5646 : }
5647 :
5648 :
5649 : /**
5650 : * Tests looking up a transfer's summary.
5651 : *
5652 : * @param exchange_url url to the exchange for the transfer.
5653 : * @param wtid identifier of the transfer.
5654 : * @param expected_order_id the id of the order associated with the transfer.
5655 : * @param expected_deposit_value the amount of the deposit made for the transfer.
5656 : * @param expected_deposit_fee the fee on the deposit made for the transfer.
5657 : * @return 1 on success, 0 otherwise.
5658 : */
5659 : static int
5660 1 : test_lookup_transfer_summary (
5661 : const char *exchange_url,
5662 : const struct TALER_WireTransferIdentifierRawP *wtid,
5663 : const char *expected_order_id,
5664 : const struct TALER_Amount *expected_deposit_value,
5665 : const struct TALER_Amount *expected_deposit_fee)
5666 : {
5667 1 : struct TestLookupTransferSummary_Closure cmp = {
5668 : .order_id = expected_order_id,
5669 : .deposit_value = expected_deposit_value,
5670 : .deposit_fee = expected_deposit_fee,
5671 : .result = 0
5672 : };
5673 1 : if (1 != TALER_MERCHANTDB_iterate_transfer_summaries (pg,
5674 : exchange_url,
5675 : wtid,
5676 : &lookup_transfer_summary_cb,
5677 : &cmp))
5678 : {
5679 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
5680 : "Lookup transfer summary failed\n");
5681 0 : return 1;
5682 : }
5683 1 : if (0 != cmp.result)
5684 : {
5685 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
5686 : "Lookup transfer summary failed: mismatched data\n");
5687 0 : return 1;
5688 : }
5689 1 : return 0;
5690 : }
5691 :
5692 :
5693 : /**
5694 : * Closure for testing 'iterate_transfer_details'.
5695 : */
5696 : struct TestLookupTransferDetails_Closure
5697 : {
5698 : /**
5699 : * Length of @e details_to_cmp.
5700 : */
5701 : unsigned int details_to_cmp_length;
5702 :
5703 : /**
5704 : * The details we expect to find.
5705 : */
5706 : const struct TALER_TrackTransferDetails *details_to_cmp;
5707 :
5708 : /**
5709 : * Number of results matching each detail in @e details_to_cmp.
5710 : */
5711 : unsigned int *results_matching;
5712 :
5713 : /**
5714 : * Total number of results found.
5715 : */
5716 : unsigned int results_length;
5717 : };
5718 :
5719 :
5720 : /**
5721 : * Called after 'test_lookup_transfer_details'.
5722 : *
5723 : * @param cls pointer to 'TestLookupTransferDetails_Closure'.
5724 : * @param current_offset offset within transfer details.
5725 : * @param details the details that were found.
5726 : */
5727 : static void
5728 1 : lookup_transfer_details_cb (void *cls,
5729 : unsigned int current_offset,
5730 : const struct TALER_TrackTransferDetails *details)
5731 : {
5732 1 : struct TestLookupTransferDetails_Closure *cmp = cls;
5733 1 : if (NULL == cmp)
5734 0 : return;
5735 2 : for (unsigned int i = 0; cmp->details_to_cmp_length > i; ++i)
5736 : {
5737 1 : if ((0 == GNUNET_memcmp (&cmp->details_to_cmp[i].h_contract_terms,
5738 1 : &details->h_contract_terms)) &&
5739 1 : (0 == GNUNET_memcmp (&cmp->details_to_cmp[i].coin_pub,
5740 1 : &details->coin_pub)) &&
5741 1 : (GNUNET_OK == TALER_amount_cmp_currency (
5742 1 : &cmp->details_to_cmp[i].coin_value,
5743 1 : &details->coin_value)) &&
5744 1 : (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_value,
5745 1 : &details->coin_value)) &&
5746 1 : (GNUNET_OK == TALER_amount_cmp_currency (
5747 1 : &cmp->details_to_cmp[i].coin_fee,
5748 1 : &details->coin_fee)) &&
5749 1 : (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_fee,
5750 : &details->coin_fee)))
5751 : {
5752 1 : cmp->results_matching[i] += 1;
5753 : }
5754 : }
5755 1 : cmp->results_length += 1;
5756 : }
5757 :
5758 :
5759 : /**
5760 : * Tests looking up details for a wire transfer.
5761 : *
5762 : * @param exchange_url url to the exchange.
5763 : * @param wtid id of the transfer.
5764 : * @param details_length the length of @e details.
5765 : * @param details the details we expect to be returned.
5766 : * @return 1 on success, 0 otherwise.
5767 : */
5768 : static int
5769 1 : test_lookup_transfer_details (
5770 : const char *exchange_url,
5771 : const struct TALER_WireTransferIdentifierRawP *wtid,
5772 : unsigned int details_length,
5773 : const struct TALER_TrackTransferDetails *details)
5774 1 : {
5775 1 : unsigned int results_matching[details_length];
5776 1 : struct TestLookupTransferDetails_Closure cmp = {
5777 : .details_to_cmp_length = details_length,
5778 : .details_to_cmp = details,
5779 : .results_matching = results_matching,
5780 : .results_length = 0
5781 : };
5782 1 : memset (results_matching,
5783 : 0,
5784 : sizeof (unsigned int) * details_length);
5785 1 : if (1 != TALER_MERCHANTDB_iterate_transfer_details (pg,
5786 : exchange_url,
5787 : wtid,
5788 : &lookup_transfer_details_cb,
5789 : &cmp))
5790 : {
5791 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
5792 : "Lookup transfer details failed\n");
5793 0 : return 1;
5794 : }
5795 1 : if (details_length != cmp.results_length)
5796 : {
5797 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
5798 : "Lookup transfer details failed: incorrect number of results (%d)\n",
5799 : cmp.results_length);
5800 0 : return 1;
5801 : }
5802 2 : for (unsigned int i = 0; details_length > i; ++i)
5803 : {
5804 1 : if (1 != cmp.results_matching[i])
5805 : {
5806 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
5807 : "Lookup transfer details failed: mismatched data\n");
5808 0 : return 1;
5809 : }
5810 : }
5811 1 : return 0;
5812 : }
5813 :
5814 :
5815 : /**
5816 : * Closure for 'iterate_transfer_details_by_order'.
5817 : */
5818 : struct TestLookupTransferDetailsByOrder_Closure
5819 : {
5820 : /**
5821 : * Length of @e transfers_to_cmp.
5822 : */
5823 : unsigned int transfers_to_cmp_length;
5824 :
5825 : /**
5826 : * List of transfers that we expect to find.
5827 : */
5828 : const struct TransferData *transfers_to_cmp;
5829 :
5830 : /**
5831 : * How many results match the corresponding element of @e transfers_to_cmp.
5832 : */
5833 : unsigned int *results_matching;
5834 :
5835 : /**
5836 : * Total number of results found.
5837 : */
5838 : unsigned int results_length;
5839 : };
5840 :
5841 :
5842 : /**
5843 : * Called after 'test_lookup_transfer_details_by_order'.
5844 : *
5845 : * @param cls pointer to 'TestLookupTransferDetailsByOrder_Closure'.
5846 : * @param wtid identifier of the transfer found.
5847 : * @param exchange_url exchange url of the transfer found.
5848 : * @param execution_time when the transfer found occurred.
5849 : * @param deposit_value amount of the deposit for the transfer found.
5850 : * @param deposit_fee amount of the fee for the deposit of the transfer.
5851 : * @param transfer_confirmed did the merchant confirm that a wire transfer with
5852 : * @a wtid over the total amount happened?
5853 : */
5854 : static void
5855 1 : lookup_transfer_details_order_cb (
5856 : void *cls,
5857 : const struct TALER_WireTransferIdentifierRawP *wtid,
5858 : const char *exchange_url,
5859 : struct GNUNET_TIME_Timestamp execution_time,
5860 : const struct TALER_Amount *deposit_value,
5861 : const struct TALER_Amount *deposit_fee,
5862 : bool transfer_confirmed,
5863 : uint64_t expected_transfer_serial_id)
5864 : {
5865 1 : struct TestLookupTransferDetailsByOrder_Closure *cmp = cls;
5866 :
5867 1 : if (NULL == cmp)
5868 0 : return;
5869 1 : cmp->results_length += 1;
5870 2 : for (unsigned int i = 0; i < cmp->transfers_to_cmp_length; ++i)
5871 : {
5872 : /* Right now iterate_transfer_details_by_order leaves execution_time
5873 : uninitialized */
5874 1 : if ((0 == GNUNET_memcmp (&cmp->transfers_to_cmp[i].wtid,
5875 1 : wtid)) &&
5876 1 : (0 == strcmp (cmp->transfers_to_cmp[i].exchange_url,
5877 1 : exchange_url)) &&
5878 : (GNUNET_OK ==
5879 1 : TALER_amount_cmp_currency (
5880 1 : &cmp->transfers_to_cmp[i].data.total_amount,
5881 1 : deposit_value)) &&
5882 : (0 ==
5883 1 : TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
5884 1 : deposit_value)) &&
5885 : (GNUNET_OK ==
5886 1 : TALER_amount_cmp_currency (
5887 1 : &cmp->transfers_to_cmp[i].deposit_fee,
5888 1 : deposit_fee)) &&
5889 : (0 ==
5890 1 : TALER_amount_cmp (&cmp->transfers_to_cmp[i].deposit_fee,
5891 : deposit_fee)) )
5892 1 : cmp->results_matching[i] += 1;
5893 : }
5894 : }
5895 :
5896 :
5897 : /**
5898 : * Tests looking up wire transfers associated with an order.
5899 : *
5900 : * @param order_serial the order to be queried.
5901 : * @param transfers_length length of @e transfers.
5902 : * @param transfers the transfers we expect to be found.
5903 : * @return 0 on success, 1 otherwise.
5904 : */
5905 : static int
5906 1 : test_lookup_transfer_details_by_order (
5907 : uint64_t order_serial,
5908 : unsigned int transfers_length,
5909 : const struct TransferData *transfers)
5910 1 : {
5911 1 : unsigned int results_matching[transfers_length];
5912 1 : struct TestLookupTransferDetailsByOrder_Closure cmp = {
5913 : .transfers_to_cmp_length = transfers_length,
5914 : .transfers_to_cmp = transfers,
5915 : .results_matching = results_matching,
5916 : .results_length = 0
5917 : };
5918 1 : memset (results_matching,
5919 : 0,
5920 : sizeof (unsigned int) * transfers_length);
5921 1 : if (transfers_length !=
5922 1 : TALER_MERCHANTDB_iterate_transfer_details_by_order (
5923 : pg,
5924 : order_serial,
5925 : &lookup_transfer_details_order_cb,
5926 : &cmp))
5927 : {
5928 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
5929 : "Lookup transfer details by order failed\n");
5930 0 : return 1;
5931 : }
5932 1 : if (transfers_length != cmp.results_length)
5933 : {
5934 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
5935 : "Lookup transfer details by order failed: incorrect number of results\n");
5936 0 : return 1;
5937 : }
5938 2 : for (unsigned int i = 0; i < transfers_length; ++i)
5939 : {
5940 1 : if (1 != cmp.results_matching[i])
5941 : {
5942 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
5943 : "Lookup transfer details by order failed: mismatched data\n");
5944 0 : return 1;
5945 : }
5946 : }
5947 1 : return 0;
5948 : }
5949 :
5950 :
5951 : /**
5952 : * Tests inserting wire fee data for an exchange.
5953 : *
5954 : * @param signkey the signing key for the exchange.
5955 : * @param wire_fee the wire fee data.
5956 : * @param expected_result what the database should return.
5957 : * @return 0 on success, 1 otherwise.
5958 : */
5959 : static int
5960 4 : test_insert_wire_fee (const struct ExchangeSignkeyData *signkey,
5961 : const struct WireFeeData *wire_fee,
5962 : enum GNUNET_DB_QueryStatus expected_result)
5963 : {
5964 4 : TEST_COND_RET_ON_FAIL (expected_result ==
5965 : TALER_MERCHANTDB_insert_exchange_wire_fee (
5966 : pg,
5967 : &signkey->master_pub,
5968 : &wire_fee->h_wire_method,
5969 : &wire_fee->fees,
5970 : wire_fee->wire_fee_start,
5971 : wire_fee->wire_fee_end,
5972 : &wire_fee->fee_sig),
5973 : "Store wire fee by exchange failed\n");
5974 4 : return 0;
5975 : }
5976 :
5977 :
5978 : /**
5979 : * Tests looking up wire fee data for an exchange.
5980 : *
5981 : * @param signkey the signing key to use for lookup.
5982 : * @param wire_fee_data the wire fee data we expect to find.
5983 : * @return 0 on success, 1 otherwise.
5984 : */
5985 : static int
5986 3 : test_lookup_wire_fee (const struct ExchangeSignkeyData *signkey,
5987 : const struct WireFeeData *wire_fee_data)
5988 : {
5989 : struct TALER_WireFeeSet fees;
5990 : struct GNUNET_TIME_Timestamp start_date;
5991 : struct GNUNET_TIME_Timestamp end_date;
5992 : struct TALER_MasterSignatureP master_sig;
5993 6 : if (1 != TALER_MERCHANTDB_get_exchange_wire_fee (pg,
5994 : &signkey->master_pub,
5995 3 : wire_fee_data->wire_method,
5996 : GNUNET_TIME_timestamp_get (),
5997 : &fees,
5998 : &start_date,
5999 : &end_date,
6000 : &master_sig))
6001 : {
6002 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
6003 : "Lookup wire fee failed\n");
6004 0 : return 1;
6005 : }
6006 3 : if ((0 !=
6007 3 : TALER_wire_fee_set_cmp (&wire_fee_data->fees,
6008 3 : &fees)) ||
6009 3 : (GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_start,
6010 : !=,
6011 3 : start_date)) ||
6012 3 : (GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_end,
6013 : !=,
6014 3 : end_date)) ||
6015 3 : (0 != GNUNET_memcmp (&wire_fee_data->fee_sig,
6016 : &master_sig)))
6017 : {
6018 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
6019 : "Lookup wire fee failed: mismatched data\n");
6020 0 : return 1;
6021 : }
6022 3 : return 0;
6023 : }
6024 :
6025 :
6026 : /**
6027 : * Closure for 'iterate_transfers'.
6028 : */
6029 : struct TestLookupTransfers_Closure
6030 : {
6031 : /**
6032 : * Length of @e transfers_to_cmp.
6033 : */
6034 : unsigned int transfers_to_cmp_length;
6035 :
6036 : /**
6037 : * The transfers we expect to find.
6038 : */
6039 : const struct TransferData *transfers_to_cmp;
6040 :
6041 : /**
6042 : * Number of results matching each transfer.
6043 : */
6044 : unsigned int *results_matching;
6045 :
6046 : /**
6047 : * Total number of results found.
6048 : */
6049 : unsigned int results_length;
6050 : };
6051 :
6052 :
6053 : /**
6054 : * Function called after 'test_lookup_transfers'.
6055 : *
6056 : * @param cls pointer to 'TestLookupTransfers_Closure'.
6057 : * @param credit_amount how much was wired to the merchant (minus fees)
6058 : * @param wtid wire transfer identifier
6059 : * @param payto_uri target account that received the wire transfer
6060 : * @param exchange_payto_uri account of the exchange that was debited,
6061 : * `full_payto` NULL if unknown; unused here
6062 : * @param exchange_url base URL of the exchange that made the wire transfer
6063 : * @param transfer_serial_id serial number identifying the transfer in the backend
6064 : * @param expected_transfer_serial_id serial number identifying the expected transfer in the backend, 0 if not @a expected
6065 : * @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_TS
6066 : * if it did not yet happen
6067 : * @param expected true if the merchant acknowledged the wire transfer reception
6068 : */
6069 : static void
6070 1 : lookup_transfers_cb (void *cls,
6071 : const struct TALER_Amount *credit_amount,
6072 : const struct TALER_WireTransferIdentifierRawP *wtid,
6073 : struct TALER_FullPayto payto_uri,
6074 : struct TALER_FullPayto exchange_payto_uri,
6075 : const char *exchange_url,
6076 : uint64_t transfer_serial_id,
6077 : uint64_t expected_transfer_serial_id,
6078 : struct GNUNET_TIME_Absolute execution_time,
6079 : bool expected)
6080 : {
6081 1 : struct TestLookupTransfers_Closure *cmp = cls;
6082 1 : if (NULL == cmp)
6083 0 : return;
6084 2 : for (unsigned int i = 0; cmp->transfers_to_cmp_length > i; ++i)
6085 : {
6086 1 : if ( (GNUNET_OK ==
6087 1 : TALER_amount_cmp_currency (
6088 1 : &cmp->transfers_to_cmp[i].data.total_amount,
6089 1 : credit_amount)) &&
6090 1 : (0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
6091 : credit_amount)) )
6092 : {
6093 1 : cmp->results_matching[i]++;
6094 : }
6095 : }
6096 1 : cmp->results_length++;
6097 : }
6098 :
6099 :
6100 : /**
6101 : * Tests looking up transfers from the database.
6102 : *
6103 : * @param instance the instance to lookup from.
6104 : * @param account the account the transfer was made to.
6105 : * @param before do not return transfers before this time.
6106 : * @param after do not return transfers after this time.
6107 : * @param limit maximum number of transfers to return.
6108 : * @param offset row in the database to start with.
6109 : * @param filter_verified how to filter verified transfers.
6110 : * @param transfers_length length of @e transfers.
6111 : * @param transfers the transfers we expect to find.
6112 : * @return 0 on success, 1 otherwise.
6113 : */
6114 : static int
6115 1 : test_lookup_transfers (const struct InstanceData *instance,
6116 : const struct TALER_MERCHANTDB_AccountDetails *account,
6117 : struct GNUNET_TIME_Timestamp before,
6118 : struct GNUNET_TIME_Timestamp after,
6119 : int64_t limit,
6120 : uint64_t offset,
6121 : unsigned int transfers_length,
6122 : const struct TransferData *transfers)
6123 1 : {
6124 1 : unsigned int results_matching[transfers_length];
6125 1 : struct TestLookupTransfers_Closure cmp = {
6126 : .transfers_to_cmp_length = transfers_length,
6127 : .transfers_to_cmp = transfers,
6128 : .results_matching = results_matching,
6129 : .results_length = 0
6130 : };
6131 1 : memset (results_matching,
6132 : 0,
6133 : sizeof (unsigned int) * transfers_length);
6134 1 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
6135 1 : if (1 != TALER_MERCHANTDB_iterate_transfers (pg,
6136 1 : instance->instance.id,
6137 : account->payto_uri,
6138 : before,
6139 : after,
6140 : limit,
6141 : offset,
6142 : TALER_EXCHANGE_YNA_ALL,
6143 : &lookup_transfers_cb,
6144 : &cmp))
6145 : {
6146 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
6147 : "Lookup transfers failed\n");
6148 0 : return 1;
6149 : }
6150 1 : if (transfers_length != cmp.results_length)
6151 : {
6152 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
6153 : "Lookup transfers failed: incorrect number of results\n");
6154 0 : return 1;
6155 : }
6156 2 : for (unsigned int i = 0; transfers_length > i; ++i)
6157 : {
6158 1 : if (1 != cmp.results_matching[i])
6159 : {
6160 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
6161 : "Lookup transfers failed: mismatched data\n");
6162 0 : return 1;
6163 : }
6164 : }
6165 1 : return 0;
6166 : }
6167 :
6168 :
6169 : /**
6170 : * Tests inserting a transfer into the database.
6171 : *
6172 : * @param instance the instance to use.
6173 : * @param account the account to transfer to.
6174 : * @param transfer the transfer to insert.
6175 : * @param expected_result the result we expect the db to return.
6176 : * @return 0 on success, 1 otherwise.
6177 : */
6178 : static int
6179 2 : test_insert_transfer (const struct InstanceData *instance,
6180 : const struct TALER_MERCHANTDB_AccountDetails *account,
6181 : const struct TransferData *transfer,
6182 : enum GNUNET_DB_QueryStatus expected_result)
6183 : {
6184 : bool no_account;
6185 : bool conflict;
6186 :
6187 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
6188 2 : TEST_COND_RET_ON_FAIL (expected_result ==
6189 : TALER_MERCHANTDB_insert_transfer (
6190 : pg,
6191 : instance->instance.id,
6192 : transfer->exchange_url,
6193 : &transfer->wtid,
6194 : &transfer->data.total_amount,
6195 : account->payto_uri,
6196 : transfer->confirmed,
6197 : &no_account,
6198 : &conflict),
6199 : "Insert transfer failed\n");
6200 2 : return 0;
6201 : }
6202 :
6203 :
6204 : /**
6205 : * Tests that inserting a transfer crediting a payto URI which belongs to
6206 : * no account of the instance is reported via the @e no_account flag,
6207 : * instead of being silently attributed to an arbitrary account.
6208 : *
6209 : * Regression test: the account lookup in insert_transfer.sql compared
6210 : * REGEXP_REPLACE(payto_uri,'\\?.*',''), a pattern which matches an
6211 : * *optional* backslash followed by anything. It therefore matched at
6212 : * offset 0, reduced every URI to the empty string, and made the
6213 : * comparison unconditionally true.
6214 : *
6215 : * @param instance the instance to use.
6216 : * @param transfer the transfer to attempt to insert.
6217 : * @return 0 on success, 1 otherwise.
6218 : */
6219 : static int
6220 1 : test_insert_transfer_unknown_account (
6221 : const struct InstanceData *instance,
6222 : const struct TransferData *transfer)
6223 : {
6224 1 : bool no_account = false;
6225 1 : bool conflict = false;
6226 1 : struct TALER_FullPayto unknown = {
6227 : .full_payto = (char *) "payto://iban/DE/NO-SUCH-ACCOUNT"
6228 : "?receiver-name=Not+My+Account"
6229 : };
6230 :
6231 1 : TEST_SET_INSTANCE (instance->instance.id,
6232 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
6233 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
6234 : TALER_MERCHANTDB_insert_transfer (
6235 : pg,
6236 : instance->instance.id,
6237 : transfer->exchange_url,
6238 : &transfer->wtid,
6239 : &transfer->data.total_amount,
6240 : unknown,
6241 : transfer->confirmed,
6242 : &no_account,
6243 : &conflict),
6244 : "Insert transfer for unknown account failed\n");
6245 1 : TEST_COND_RET_ON_FAIL (no_account,
6246 : "Insert transfer accepted a payto URI belonging to "
6247 : "no account of the instance\n");
6248 1 : return 0;
6249 : }
6250 :
6251 :
6252 : /**
6253 : * Tests linking a deposit to a transfer.
6254 : *
6255 : * @param instance the instance that the deposit and transfer are for.
6256 : * @param signkey the signing used on the deposit.
6257 : * @param order the order the deposit and transfer were made for.
6258 : * @param transfer the transfer.
6259 : * @param expected_result the result the database should return.
6260 : * @param expected_cleared clearance status the database should return.
6261 : * @return 0 on success, 1 otherwise.
6262 : */
6263 : static int
6264 8 : test_insert_deposit_to_transfer (const struct InstanceData *instance,
6265 : const struct ExchangeSignkeyData *signkey,
6266 : const struct OrderData *order,
6267 : const struct DepositData *deposit,
6268 : const struct TransferData *transfer,
6269 : enum GNUNET_DB_QueryStatus expected_result,
6270 : bool expect_cleared)
6271 : {
6272 8 : const struct TALER_EXCHANGE_DepositData deposit_data = {
6273 : .exchange_pub = signkey->exchange_pub,
6274 : .exchange_sig = deposit->exchange_sig,
6275 : .wtid = transfer->wtid,
6276 : .execution_time = transfer->data.execution_time,
6277 : .coin_contribution = deposit->amount_with_fee
6278 : };
6279 : uint64_t deposit_serial;
6280 :
6281 8 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
6282 8 : deposit_serial = get_deposit_serial (instance,
6283 : order,
6284 : deposit);
6285 :
6286 8 : TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_DTTS_SETTLED ==
6287 : TALER_MERCHANTDB_insert_deposit_to_transfer (
6288 : pg,
6289 : deposit_serial,
6290 : &deposit->h_wire,
6291 : deposit->exchange_url,
6292 : &deposit_data),
6293 : "insert deposit to transfer failed\n");
6294 8 : return 0;
6295 : }
6296 :
6297 :
6298 : /**
6299 : * Tests linking a deposit to a transfer that the exchange claims to
6300 : * have wired to an account we do not know. This is a *permanent*
6301 : * failure: the caller must be able to tell it apart from a successful
6302 : * settlement, and the deposit must not make its order count as wired.
6303 : *
6304 : * @param instance the instance that the deposit and transfer are for.
6305 : * @param signkey the signing key used on the deposit.
6306 : * @param order the order the deposit was made for.
6307 : * @param deposit the deposit.
6308 : * @param transfer the transfer the exchange claims to have made.
6309 : * @return 0 on success, 1 otherwise.
6310 : */
6311 : static int
6312 1 : test_insert_deposit_to_unknown_account (
6313 : const struct InstanceData *instance,
6314 : const struct ExchangeSignkeyData *signkey,
6315 : const struct OrderData *order,
6316 : const struct DepositData *deposit,
6317 : const struct TransferData *transfer)
6318 : {
6319 1 : const struct TALER_EXCHANGE_DepositData deposit_data = {
6320 : .exchange_pub = signkey->exchange_pub,
6321 : .exchange_sig = deposit->exchange_sig,
6322 : .wtid = transfer->wtid,
6323 : .execution_time = transfer->data.execution_time,
6324 : .coin_contribution = deposit->amount_with_fee
6325 : };
6326 : struct TALER_MerchantWireHashP bogus_h_wire;
6327 : uint64_t deposit_serial;
6328 :
6329 1 : GNUNET_CRYPTO_random_block (&bogus_h_wire,
6330 : sizeof (bogus_h_wire));
6331 1 : TEST_SET_INSTANCE (instance->instance.id,
6332 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
6333 1 : deposit_serial = get_deposit_serial (instance,
6334 : order,
6335 : deposit);
6336 1 : TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_DTTS_ACCOUNT_UNKNOWN ==
6337 : TALER_MERCHANTDB_insert_deposit_to_transfer (
6338 : pg,
6339 : deposit_serial,
6340 : &bogus_h_wire,
6341 : deposit->exchange_url,
6342 : &deposit_data),
6343 : "insert deposit to transfer with an unknown target account must report ACCOUNT_UNKNOWN\n");
6344 1 : return 0;
6345 : }
6346 :
6347 :
6348 : /**
6349 : * Inserts details for a transfer into the database.
6350 : *
6351 : * @param instance the instance the transfer is in.
6352 : * @param account the destination account for the transfer.
6353 : * @param transfer the transfer we are adding details to.
6354 : * @param expected_result the result expected from the db.
6355 : * @return 0 on success, 1 otherwise.
6356 : */
6357 : static int
6358 3 : test_insert_transfer_details (
6359 : const struct InstanceData *instance,
6360 : const struct TALER_MERCHANTDB_AccountDetails *account,
6361 : const struct TransferData *transfer,
6362 : enum GNUNET_DB_QueryStatus expected_result)
6363 : {
6364 3 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
6365 3 : TEST_COND_RET_ON_FAIL (expected_result ==
6366 : TALER_MERCHANTDB_insert_transfer_details (
6367 : pg,
6368 : instance->instance.id,
6369 : transfer->exchange_url,
6370 : account->payto_uri,
6371 : &transfer->wtid,
6372 : &transfer->data,
6373 : NULL),
6374 : "Insert transfer details failed\n");
6375 3 : return 0;
6376 : }
6377 :
6378 :
6379 : /**
6380 : * Inserts details for a transfer into the database and checks how
6381 : * many orders were reported as fully settled by it. An aggregated
6382 : * transfer settles several orders at once and every one of them must
6383 : * be reported, otherwise clients long-polling on the others never
6384 : * learn that the order was wired.
6385 : *
6386 : * @param instance the instance the transfer is in.
6387 : * @param account the destination account for the transfer.
6388 : * @param transfer the transfer we are adding details to.
6389 : * @param expected_result the result expected from the db.
6390 : * @param expected_settled number of orders that must be reported.
6391 : * @return 0 on success, 1 otherwise.
6392 : */
6393 : static int
6394 2 : test_insert_transfer_details_settling (
6395 : const struct InstanceData *instance,
6396 : const struct TALER_MERCHANTDB_AccountDetails *account,
6397 : const struct TransferData *transfer,
6398 : enum GNUNET_DB_QueryStatus expected_result,
6399 : unsigned int expected_settled)
6400 : {
6401 2 : unsigned int num_settled = 0;
6402 :
6403 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
6404 2 : TEST_COND_RET_ON_FAIL (expected_result ==
6405 : TALER_MERCHANTDB_insert_transfer_details (
6406 : pg,
6407 : instance->instance.id,
6408 : transfer->exchange_url,
6409 : account->payto_uri,
6410 : &transfer->wtid,
6411 : &transfer->data,
6412 : &num_settled),
6413 : "Insert transfer details failed\n");
6414 2 : TEST_COND_RET_ON_FAIL (expected_settled == num_settled,
6415 : "Wrong number of settled orders reported\n");
6416 2 : return 0;
6417 : }
6418 :
6419 :
6420 : /**
6421 : * State used to count notifications that wake the webhook helper.
6422 : */
6423 : struct WebhookPendingEventState
6424 : {
6425 : /**
6426 : * Number of notifications received.
6427 : */
6428 : unsigned int fired;
6429 : };
6430 :
6431 :
6432 : /**
6433 : * Count a notification for a newly pending webhook.
6434 : *
6435 : * @param cls a `struct WebhookPendingEventState *`
6436 : * @param extra unused notification payload
6437 : * @param extra_size size of @a extra
6438 : */
6439 : static void
6440 1 : webhook_pending_event_cb (void *cls,
6441 : const void *extra,
6442 : size_t extra_size)
6443 : {
6444 1 : struct WebhookPendingEventState *state = cls;
6445 :
6446 : (void) extra;
6447 : (void) extra_size;
6448 1 : state->fired++;
6449 1 : }
6450 :
6451 :
6452 : /**
6453 : * Container for data used when testing transfers.
6454 : */
6455 : struct TestTransfers_Closure
6456 : {
6457 : /**
6458 : * The instance.
6459 : */
6460 : struct InstanceData instance;
6461 :
6462 : /**
6463 : * The account.
6464 : */
6465 : struct TALER_MERCHANTDB_AccountDetails account;
6466 :
6467 : /**
6468 : * The exchange signing key.
6469 : */
6470 : struct ExchangeSignkeyData signkey;
6471 :
6472 : /**
6473 : * The order data.
6474 : */
6475 : struct OrderData order;
6476 :
6477 : /**
6478 : * Two more orders, both settled by one aggregated transfer.
6479 : */
6480 : struct OrderData orders_agg[2];
6481 :
6482 : /**
6483 : * Order with one deposit that settles and one that fails
6484 : * permanently.
6485 : */
6486 : struct OrderData order_perm;
6487 :
6488 : /**
6489 : * The deposit data.
6490 : */
6491 : struct DepositData deposit;
6492 :
6493 : /**
6494 : * A second deposit for the same order, used to test an
6495 : * exchange response that lists the same coin twice.
6496 : */
6497 : struct DepositData deposit2;
6498 :
6499 : /**
6500 : * One deposit for each of @e orders_agg.
6501 : */
6502 : struct DepositData deposits_agg[2];
6503 :
6504 : /**
6505 : * Two deposits for @e order_perm: the first one settles, the
6506 : * second one is wired to an account we do not know.
6507 : */
6508 : struct DepositData deposits_perm[2];
6509 :
6510 : /**
6511 : * Wire fee data.
6512 : */
6513 : struct WireFeeData wire_fee[2];
6514 :
6515 : /**
6516 : * The transfers.
6517 : */
6518 : struct TransferData transfers[5];
6519 : };
6520 :
6521 :
6522 : /**
6523 : * Prepares for testing transfers.
6524 : *
6525 : * @param cls the test data.
6526 : */
6527 : static void
6528 1 : pre_test_transfers (struct TestTransfers_Closure *cls)
6529 : {
6530 : /* Instance */
6531 1 : make_instance ("test_inst_transfers",
6532 : &cls->instance);
6533 :
6534 : /* Account */
6535 1 : make_account (&cls->account);
6536 1 : cls->account.instance_id = cls->instance.instance.id;
6537 : /* Order */
6538 1 : make_order ("test_transfers_od_1",
6539 : &cls->order);
6540 :
6541 : /* Signing key */
6542 1 : make_exchange_signkey (&cls->signkey);
6543 :
6544 : /* Deposit */
6545 1 : make_deposit (&cls->instance,
6546 1 : &cls->account,
6547 1 : &cls->order,
6548 1 : &cls->signkey,
6549 : &cls->deposit);
6550 :
6551 : /* Wire fee */
6552 1 : make_wire_fee (&cls->signkey,
6553 : &cls->wire_fee[0]);
6554 1 : make_wire_fee (&cls->signkey,
6555 : &cls->wire_fee[1]);
6556 1 : cls->wire_fee[1].wire_method = "wire-method-2";
6557 1 : GNUNET_CRYPTO_hash (cls->wire_fee[1].wire_method,
6558 1 : strlen (cls->wire_fee[1].wire_method) + 1,
6559 : &cls->wire_fee[1].h_wire_method);
6560 :
6561 : /* Transfers */
6562 1 : make_transfer (&cls->signkey,
6563 : 1,
6564 1 : &cls->deposit,
6565 : &cls->transfers[0]);
6566 1 : cls->transfers[0].confirmed = true;
6567 : /* A transfer the exchange reports without any deposits. */
6568 1 : make_transfer (&cls->signkey,
6569 : 0,
6570 1 : &cls->deposit,
6571 : &cls->transfers[1]);
6572 1 : cls->transfers[1].confirmed = true;
6573 : /* A transfer where the exchange lists the same coin twice. */
6574 1 : make_deposit (&cls->instance,
6575 1 : &cls->account,
6576 1 : &cls->order,
6577 1 : &cls->signkey,
6578 : &cls->deposit2);
6579 : {
6580 : struct DepositData dup[2];
6581 :
6582 1 : dup[0] = cls->deposit2;
6583 1 : dup[1] = cls->deposit2;
6584 1 : make_transfer (&cls->signkey,
6585 : 2,
6586 : dup,
6587 : &cls->transfers[2]);
6588 : }
6589 1 : cls->transfers[2].confirmed = true;
6590 : /* Two orders settled by one aggregated wire transfer. */
6591 1 : make_order ("test_transfers_od_agg_1",
6592 : &cls->orders_agg[0]);
6593 1 : make_order ("test_transfers_od_agg_2",
6594 : &cls->orders_agg[1]);
6595 3 : for (unsigned int i = 0; i < 2; i++)
6596 2 : make_deposit (&cls->instance,
6597 2 : &cls->account,
6598 2 : &cls->orders_agg[i],
6599 2 : &cls->signkey,
6600 : &cls->deposits_agg[i]);
6601 1 : make_transfer (&cls->signkey,
6602 : 2,
6603 1 : cls->deposits_agg,
6604 : &cls->transfers[3]);
6605 1 : cls->transfers[3].confirmed = true;
6606 : /* An order with two deposits, one of which the exchange wires to an
6607 : account we do not know (permanent failure). */
6608 1 : make_order ("test_transfers_od_perm",
6609 : &cls->order_perm);
6610 3 : for (unsigned int i = 0; i < 2; i++)
6611 2 : make_deposit (&cls->instance,
6612 2 : &cls->account,
6613 2 : &cls->order_perm,
6614 2 : &cls->signkey,
6615 : &cls->deposits_perm[i]);
6616 1 : make_transfer (&cls->signkey,
6617 : 1,
6618 1 : cls->deposits_perm,
6619 : &cls->transfers[4]);
6620 1 : cls->transfers[4].confirmed = true;
6621 1 : }
6622 :
6623 :
6624 : /**
6625 : * Cleans up after testing transfers.
6626 : *
6627 : * @param cls the test data.
6628 : */
6629 : static void
6630 1 : post_test_transfers (struct TestTransfers_Closure *cls)
6631 : {
6632 1 : GNUNET_array_grow (cls->transfers->data.details,
6633 : cls->transfers->data.details_length,
6634 : 0);
6635 1 : GNUNET_array_grow (cls->transfers[2].data.details,
6636 : cls->transfers[2].data.details_length,
6637 : 0);
6638 1 : GNUNET_array_grow (cls->transfers[3].data.details,
6639 : cls->transfers[3].data.details_length,
6640 : 0);
6641 1 : GNUNET_array_grow (cls->transfers[4].data.details,
6642 : cls->transfers[4].data.details_length,
6643 : 0);
6644 1 : free_instance_data (&cls->instance);
6645 1 : free_order_data (&cls->order);
6646 1 : free_order_data (&cls->orders_agg[0]);
6647 1 : free_order_data (&cls->orders_agg[1]);
6648 1 : free_order_data (&cls->order_perm);
6649 1 : }
6650 :
6651 :
6652 : /**
6653 : * Runs the tests for transfers.
6654 : *
6655 : * @param cls the test data.
6656 : * @return 0 on success, 1 otherwise.
6657 : */
6658 : static int
6659 1 : run_test_transfers (struct TestTransfers_Closure *cls)
6660 : {
6661 : uint64_t order_serial;
6662 : struct TALER_WireFeeSet fees;
6663 :
6664 : /* Test lookup wire fee fails when it isn't in the db */
6665 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
6666 : TALER_MERCHANTDB_get_exchange_wire_fee (pg,
6667 : &cls->signkey.master_pub,
6668 : cls->wire_fee[0].wire_method,
6669 : GNUNET_TIME_timestamp_get (),
6670 : &fees,
6671 : NULL,
6672 : NULL,
6673 : NULL),
6674 : "Lookup wire fee failed\n");
6675 : /* Test store wire fee by exchange */
6676 1 : TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
6677 : &cls->wire_fee[0],
6678 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6679 : /* Test double insertion fails */
6680 1 : TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
6681 : &cls->wire_fee[0],
6682 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
6683 : /* Test lookup wire fee by exchange */
6684 1 : TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey,
6685 : &cls->wire_fee[0]));
6686 : /* A *different* fee for the same (master_pub, h_wire_method,
6687 : start_date) must be reported, not silently dropped: the stored fee
6688 : is the one the exchange signed, and continuing to compute with a
6689 : stale fee is an accounting error. Regression test: the INSERT used
6690 : a bare 'ON CONFLICT DO NOTHING' and returned NO_RESULTS here. */
6691 : {
6692 1 : struct WireFeeData changed = cls->wire_fee[0];
6693 :
6694 1 : GNUNET_assert (GNUNET_OK ==
6695 : TALER_string_to_amount ("EUR:0.99",
6696 : &changed.fees.wire));
6697 1 : TALER_exchange_offline_wire_fee_sign (changed.wire_method,
6698 : changed.wire_fee_start,
6699 : changed.wire_fee_end,
6700 : &changed.fees,
6701 1 : &cls->signkey.master_priv,
6702 : &changed.fee_sig);
6703 1 : TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
6704 : &changed,
6705 : GNUNET_DB_STATUS_HARD_ERROR));
6706 : /* ... and the fee on file must be untouched. */
6707 1 : TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey,
6708 : &cls->wire_fee[0]));
6709 : }
6710 : /* Test different wire fees for different methods. */
6711 1 : TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
6712 : &cls->wire_fee[1],
6713 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6714 1 : TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey,
6715 : &cls->wire_fee[1]));
6716 : /* Insert the instance */
6717 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
6718 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6719 : /* Insert the account */
6720 1 : TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
6721 : &cls->account,
6722 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6723 : /* Insert a signing key */
6724 1 : TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
6725 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6726 : /* Insert an order */
6727 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
6728 : &cls->order,
6729 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6730 : /* Insert contract terms */
6731 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
6732 : &cls->order,
6733 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6734 1 : order_serial = get_order_serial (&cls->instance,
6735 1 : &cls->order);
6736 : /* Insert the deposit */
6737 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
6738 : &cls->signkey,
6739 : &cls->deposit,
6740 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6741 : /* Mark as paid, otherwise wiring doesn't make sense. */
6742 1 : TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
6743 : &cls->order,
6744 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6745 : /* A payto URI that matches no account must be rejected, not silently
6746 : attributed to some arbitrary account of the instance. */
6747 1 : TEST_RET_ON_FAIL (test_insert_transfer_unknown_account (&cls->instance,
6748 : &cls->transfers[0]));
6749 : /* Insert the transfer */
6750 1 : TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance,
6751 : &cls->account,
6752 : &cls->transfers[0],
6753 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6754 1 : TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance,
6755 : &cls->account,
6756 : &cls->transfers[0],
6757 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6758 1 : TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
6759 : &cls->signkey,
6760 : &cls->order,
6761 : &cls->deposit,
6762 : &cls->transfers[0],
6763 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
6764 : false));
6765 1 : TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
6766 : &cls->signkey,
6767 : &cls->order,
6768 : &cls->deposit,
6769 : &cls->transfers[0],
6770 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
6771 : false));
6772 1 : TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance,
6773 : &cls->account,
6774 : &cls->transfers[0],
6775 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6776 : /* Inserting deposits/transfers will automatically mark as wired. */
6777 1 : TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
6778 : cls->order.id,
6779 : NULL,
6780 : true,
6781 : true));
6782 1 : TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
6783 : &cls->signkey,
6784 : &cls->order,
6785 : &cls->deposit,
6786 : &cls->transfers[0],
6787 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
6788 : true));
6789 :
6790 1 : TEST_RET_ON_FAIL (test_lookup_transfer_summary (cls->deposit.exchange_url,
6791 : &cls->transfers[0].wtid,
6792 : cls->order.id,
6793 : &cls->deposit.amount_with_fee,
6794 : &cls->deposit.deposit_fee));
6795 1 : TEST_RET_ON_FAIL (test_lookup_transfer_details (cls->deposit.exchange_url,
6796 : &cls->transfers[0].wtid,
6797 : 1,
6798 : &cls->transfers[0].data.
6799 : details[0]));
6800 1 : TEST_RET_ON_FAIL (test_lookup_transfer_details_by_order (order_serial,
6801 : 1,
6802 : &cls->transfers[0]));
6803 1 : TEST_RET_ON_FAIL (test_lookup_transfers (&cls->instance,
6804 : &cls->account,
6805 : GNUNET_TIME_UNIT_FOREVER_TS,
6806 : GNUNET_TIME_UNIT_ZERO_TS,
6807 : 8,
6808 : 0,
6809 : 1,
6810 : &cls->transfers[0]));
6811 : /* The exchange is allowed to report a wire transfer with an empty
6812 : list of deposits; that must not fail the transaction (which used
6813 : to abort taler-merchant-reconciliation for all instances).
6814 : The deposit-to-transfer insert is what makes us expect the
6815 : transfer in the first place, so that we get past the account
6816 : lookup and actually reach the per-coin loop. */
6817 1 : TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
6818 : &cls->signkey,
6819 : &cls->order,
6820 : &cls->deposit,
6821 : &cls->transfers[1],
6822 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
6823 : false));
6824 1 : TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance,
6825 : &cls->account,
6826 : &cls->transfers[1],
6827 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6828 : /* The exchange may list the same coin twice in one response;
6829 : merchant_expected_transfer_to_coin is UNIQUE on deposit_serial,
6830 : so this used to be a hard error killing the reconciliation
6831 : daemon for all instances. */
6832 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
6833 : &cls->signkey,
6834 : &cls->deposit2,
6835 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6836 1 : TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
6837 : &cls->signkey,
6838 : &cls->order,
6839 : &cls->deposit2,
6840 : &cls->transfers[2],
6841 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
6842 : false));
6843 1 : TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance,
6844 : &cls->account,
6845 : &cls->transfers[2],
6846 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6847 : /* One aggregated transfer settles two orders: BOTH of them must be
6848 : reported, otherwise clients long-polling on the order that is not
6849 : reported never learn that it was wired. */
6850 3 : for (unsigned int i = 0; i < 2; i++)
6851 : {
6852 2 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
6853 : &cls->orders_agg[i],
6854 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6855 2 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
6856 : &cls->orders_agg[i],
6857 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6858 2 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
6859 : &cls->signkey,
6860 : &cls->deposits_agg[i],
6861 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6862 2 : TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
6863 : &cls->orders_agg[i],
6864 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
6865 2 : TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
6866 : &cls->signkey,
6867 : &cls->orders_agg[i],
6868 : &cls->deposits_agg[i],
6869 : &cls->transfers[3],
6870 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
6871 : false));
6872 : }
6873 : {
6874 : static const char *webhook_id = "test-transfer-order-settled";
6875 : static char webhook_url[]
6876 : = "https://example.com/order-settled-event-test";
6877 1 : struct TALER_MERCHANTDB_WebhookDetails wb = {
6878 : .event_type = "order_settled",
6879 : .url = webhook_url,
6880 : .http_method = "POST",
6881 : .header_template = NULL,
6882 : .body_template = "{{order_id}}"
6883 : };
6884 1 : struct GNUNET_DB_EventHeaderP es = {
6885 1 : .size = htons (sizeof (es)),
6886 1 : .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING)
6887 : };
6888 1 : struct WebhookPendingEventState event_state = { 0 };
6889 : struct GNUNET_DB_EventHandler *eh;
6890 : uint64_t pending_count;
6891 1 : int webhook_test_result = 1;
6892 :
6893 1 : TEST_SET_INSTANCE (cls->instance.instance.id,
6894 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
6895 1 : TEST_COND_RET_ON_FAIL (
6896 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
6897 : TALER_MERCHANTDB_insert_webhook (pg,
6898 : cls->instance.instance.id,
6899 : webhook_id,
6900 : &wb),
6901 : "Failed to insert order_settled webhook\n");
6902 1 : eh = TALER_MERCHANTDB_event_listen (pg,
6903 : &es,
6904 1 : GNUNET_TIME_UNIT_FOREVER_REL,
6905 : &webhook_pending_event_cb,
6906 : &event_state);
6907 1 : if (NULL == eh)
6908 : {
6909 0 : GNUNET_break (0);
6910 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
6911 : "Failed to listen for pending webhook events\n");
6912 0 : goto webhook_cleanup;
6913 : }
6914 :
6915 1 : TEST_WITH_FAIL_CLAUSE (
6916 : test_insert_transfer_details_settling (
6917 : &cls->instance,
6918 : &cls->account,
6919 : &cls->transfers[3],
6920 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
6921 : 2),
6922 : goto webhook_listener_cleanup;
6923 : );
6924 1 : GNUNET_PQ_event_do_poll (pg->conn);
6925 1 : if (1 != event_state.fired)
6926 : {
6927 0 : GNUNET_break (0);
6928 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
6929 : "Settlement generated %u pending webhook events, expected 1\n",
6930 : event_state.fired);
6931 0 : goto webhook_listener_cleanup;
6932 : }
6933 1 : TEST_WITH_FAIL_CLAUSE (
6934 : query_sql_num (
6935 : "SELECT COUNT(*) AS num"
6936 : " FROM merchant.merchant_pending_webhooks"
6937 : " WHERE url='https://example.com/order-settled-event-test'",
6938 : &pending_count),
6939 : goto webhook_listener_cleanup;
6940 : );
6941 1 : if (2 != pending_count)
6942 : {
6943 0 : GNUNET_break (0);
6944 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
6945 : "Settlement queued %llu webhooks, expected 2\n",
6946 : (unsigned long long) pending_count);
6947 0 : goto webhook_listener_cleanup;
6948 : }
6949 :
6950 : /* A deposit that failed permanently (exchange wired the money to an
6951 : account we do not know) must NOT make the order count as wired,
6952 : queue an order_settled webhook, or wake the webhook helper. */
6953 1 : TEST_WITH_FAIL_CLAUSE (
6954 : test_insert_order (&cls->instance,
6955 : &cls->order_perm,
6956 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT),
6957 : goto webhook_listener_cleanup;
6958 : );
6959 1 : TEST_WITH_FAIL_CLAUSE (
6960 : test_insert_contract_terms (&cls->instance,
6961 : &cls->order_perm,
6962 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT),
6963 : goto webhook_listener_cleanup;
6964 : );
6965 3 : for (unsigned int i = 0; i < 2; i++)
6966 2 : TEST_WITH_FAIL_CLAUSE (
6967 : test_insert_deposit (&cls->instance,
6968 : &cls->signkey,
6969 : &cls->deposits_perm[i],
6970 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT),
6971 : goto webhook_listener_cleanup;
6972 : );
6973 1 : TEST_WITH_FAIL_CLAUSE (
6974 : test_mark_contract_paid (&cls->instance,
6975 : &cls->order_perm,
6976 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT),
6977 : goto webhook_listener_cleanup;
6978 : );
6979 1 : TEST_WITH_FAIL_CLAUSE (
6980 : test_insert_deposit_to_transfer (&cls->instance,
6981 : &cls->signkey,
6982 : &cls->order_perm,
6983 : &cls->deposits_perm[0],
6984 : &cls->transfers[4],
6985 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
6986 : false),
6987 : goto webhook_listener_cleanup;
6988 : );
6989 1 : TEST_WITH_FAIL_CLAUSE (
6990 : test_insert_deposit_to_unknown_account (
6991 : &cls->instance,
6992 : &cls->signkey,
6993 : &cls->order_perm,
6994 : &cls->deposits_perm[1],
6995 : &cls->transfers[4]),
6996 : goto webhook_listener_cleanup;
6997 : );
6998 1 : TEST_WITH_FAIL_CLAUSE (
6999 : test_insert_transfer_details_settling (
7000 : &cls->instance,
7001 : &cls->account,
7002 : &cls->transfers[4],
7003 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
7004 : 0),
7005 : goto webhook_listener_cleanup;
7006 : );
7007 1 : GNUNET_PQ_event_do_poll (pg->conn);
7008 1 : if (1 != event_state.fired)
7009 : {
7010 0 : GNUNET_break (0);
7011 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7012 : "Unsettled order generated a pending webhook event\n");
7013 0 : goto webhook_listener_cleanup;
7014 : }
7015 1 : TEST_WITH_FAIL_CLAUSE (
7016 : query_sql_num (
7017 : "SELECT COUNT(*) AS num"
7018 : " FROM merchant.merchant_pending_webhooks"
7019 : " WHERE url='https://example.com/order-settled-event-test'",
7020 : &pending_count),
7021 : goto webhook_listener_cleanup;
7022 : );
7023 1 : if (2 != pending_count)
7024 : {
7025 0 : GNUNET_break (0);
7026 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7027 : "Unsettled order changed the pending webhook count\n");
7028 0 : goto webhook_listener_cleanup;
7029 : }
7030 1 : TEST_WITH_FAIL_CLAUSE (
7031 : test_lookup_payment_status (cls->instance.instance.id,
7032 : cls->order_perm.id,
7033 : NULL,
7034 : true,
7035 : false),
7036 : goto webhook_listener_cleanup;
7037 : );
7038 1 : webhook_test_result = 0;
7039 :
7040 1 : webhook_listener_cleanup:
7041 1 : TALER_MERCHANTDB_event_listen_cancel (eh);
7042 1 : webhook_cleanup:
7043 1 : TEST_COND_RET_ON_FAIL (
7044 : 0 == exec_sql (
7045 : "DELETE FROM merchant.merchant_pending_webhooks"
7046 : " WHERE url='https://example.com/order-settled-event-test'"),
7047 : "Failed to delete pending order_settled webhooks\n");
7048 1 : TEST_SET_INSTANCE (cls->instance.instance.id,
7049 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
7050 1 : TEST_COND_RET_ON_FAIL (
7051 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
7052 : TALER_MERCHANTDB_delete_webhook (pg,
7053 : cls->instance.instance.id,
7054 : webhook_id),
7055 : "Failed to delete order_settled webhook\n");
7056 1 : if (0 != webhook_test_result)
7057 0 : return 1;
7058 : }
7059 1 : return 0;
7060 : }
7061 :
7062 :
7063 : /**
7064 : * Takes care of all work for testing transfers.
7065 : *
7066 : * @return 0 on success, 1 otherwise.
7067 : */
7068 : static int
7069 1 : test_transfers (void)
7070 : {
7071 : struct TestTransfers_Closure test_cls;
7072 : int test_result;
7073 :
7074 1 : pre_test_transfers (&test_cls);
7075 1 : test_result = run_test_transfers (&test_cls);
7076 1 : post_test_transfers (&test_cls);
7077 1 : return test_result;
7078 : }
7079 :
7080 :
7081 : /**
7082 : * Closure for testing iterate_refunds.
7083 : */
7084 : struct TestLookupRefunds_Closure
7085 : {
7086 : /**
7087 : * Length of @e coin_pub_to_cmp and @e refund_amount_to_cmp.
7088 : */
7089 : unsigned int refunds_to_cmp_length;
7090 :
7091 : /**
7092 : * Public keys of the refunded coins.
7093 : */
7094 : const struct TALER_CoinSpendPublicKeyP *coin_pub_to_cmp;
7095 :
7096 : /**
7097 : * Amount of each refund.
7098 : */
7099 : const struct TALER_Amount *refund_amount_to_cmp;
7100 :
7101 : /**
7102 : * Number of results matching each refund provided.
7103 : */
7104 : unsigned int *results_matching;
7105 :
7106 : /**
7107 : * Total number of results returned;
7108 : */
7109 : unsigned int results_length;
7110 : };
7111 :
7112 :
7113 : /**
7114 : * Called after test_lookup_refunds.
7115 : * @param cls pointer to a TestLookupRefunds_Closure.
7116 : * @param coin_pub the public key of the coin for the refund found.
7117 : * @param refund_amount the amount of the refund found.
7118 : */
7119 : static void
7120 1 : lookup_refunds_cb (void *cls,
7121 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
7122 : const struct TALER_Amount *refund_amount)
7123 : {
7124 1 : struct TestLookupRefunds_Closure *cmp = cls;
7125 1 : if (NULL == cmp)
7126 0 : return;
7127 1 : cmp->results_length += 1;
7128 2 : for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i)
7129 : {
7130 1 : if ((0 == GNUNET_memcmp (&cmp->coin_pub_to_cmp[i],
7131 1 : coin_pub)) &&
7132 1 : (GNUNET_OK == TALER_amount_cmp_currency (&cmp->refund_amount_to_cmp[i],
7133 1 : refund_amount)) &&
7134 1 : (0 == TALER_amount_cmp (&cmp->refund_amount_to_cmp[i],
7135 : refund_amount)))
7136 : {
7137 1 : cmp->results_matching[i] += 1;
7138 : }
7139 : }
7140 : }
7141 :
7142 :
7143 : /**
7144 : * Tests looking up refunds from the database.
7145 : * @param instance the instance to look up refunds for.
7146 : * @param h_contract_terms hash of the contract terms the refunds are for.
7147 : * @param refunds_length length of @e coin_pubs and @e refund_amounts.
7148 : * @param coin_pubs the public keys of the coins that were refunded.
7149 : * @param refund_amounts the amounts of the coins that were refunded.
7150 : *
7151 : * @return 0 on success, 1 otherwise.
7152 : */
7153 : static int
7154 1 : test_lookup_refunds (const struct InstanceData *instance,
7155 : const struct TALER_PrivateContractHashP *h_contract_terms,
7156 : unsigned int refunds_length,
7157 : const struct TALER_CoinSpendPublicKeyP *coin_pubs,
7158 : const struct TALER_Amount *refund_amounts)
7159 1 : {
7160 1 : unsigned int results_matching[refunds_length];
7161 1 : struct TestLookupRefunds_Closure cmp = {
7162 : .refunds_to_cmp_length = refunds_length,
7163 : .coin_pub_to_cmp = coin_pubs,
7164 : .refund_amount_to_cmp = refund_amounts,
7165 : .results_matching = results_matching,
7166 : .results_length = 0
7167 : };
7168 1 : memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
7169 1 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
7170 1 : if (1 != TALER_MERCHANTDB_iterate_refunds (pg,
7171 1 : instance->instance.id,
7172 : h_contract_terms,
7173 : &lookup_refunds_cb,
7174 : &cmp))
7175 : {
7176 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7177 : "Lookup refunds failed\n");
7178 0 : return 1;
7179 : }
7180 1 : if (refunds_length != cmp.results_length)
7181 : {
7182 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7183 : "Lookup refunds failed: incorrect number of results returned\n")
7184 : ;
7185 0 : return 1;
7186 : }
7187 2 : for (unsigned int i = 0; refunds_length > i; ++i)
7188 : {
7189 1 : if (1 != cmp.results_matching[i])
7190 : {
7191 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7192 : "Lookup refunds failed: mismatched data\n");
7193 0 : return 1;
7194 : }
7195 : }
7196 1 : return 0;
7197 : }
7198 :
7199 :
7200 : /**
7201 : * Container for refund data.
7202 : */
7203 : struct RefundData
7204 : {
7205 : /**
7206 : * When the refund occurred.
7207 : */
7208 : struct GNUNET_TIME_Timestamp timestamp;
7209 :
7210 : /**
7211 : * Reason for the refund.
7212 : */
7213 : const char *reason;
7214 :
7215 : /**
7216 : * Amount of the refund.
7217 : */
7218 : struct TALER_Amount refund_amount;
7219 :
7220 : /**
7221 : * Public key of the coin that was refunded.
7222 : */
7223 : const struct TALER_CoinSpendPublicKeyP *coin_pub;
7224 :
7225 : /**
7226 : * URL to the exchange that did the refund.
7227 : */
7228 : const char *exchange_url;
7229 : };
7230 :
7231 :
7232 : /**
7233 : * Creates a refund for testing with.
7234 : * @param deposit the deposit being refunded.
7235 : * @param refund the data to set.
7236 : */
7237 : static void
7238 67 : make_refund (const struct DepositData *deposit,
7239 : struct RefundData *refund)
7240 : {
7241 67 : refund->timestamp = GNUNET_TIME_timestamp_get ();
7242 67 : refund->reason = "some reason";
7243 67 : refund->refund_amount = deposit->amount_with_fee;
7244 67 : refund->coin_pub = &deposit->coin_pub;
7245 67 : refund->exchange_url = deposit->exchange_url;
7246 67 : }
7247 :
7248 :
7249 : /**
7250 : * Container for proof of a refund.
7251 : */
7252 : struct RefundProofData
7253 : {
7254 : /**
7255 : * Fee charged for the refund.
7256 : */
7257 : struct TALER_Amount refund_fee;
7258 :
7259 : /**
7260 : * The exchange's signature on the refund.
7261 : */
7262 : struct TALER_ExchangeSignatureP exchange_sig;
7263 : };
7264 :
7265 :
7266 : /**
7267 : * Closure for testing iterate_refunds_detailed.
7268 : */
7269 : struct TestLookupRefundsDetailed_Closure
7270 : {
7271 : /**
7272 : * Length of @e refunds_to_cmp.
7273 : */
7274 : unsigned int refunds_to_cmp_length;
7275 :
7276 : /**
7277 : * The refunds we expect to find.
7278 : */
7279 : const struct RefundData *refunds_to_cmp;
7280 :
7281 : /**
7282 : * Whether to compare the timestamps or not (if we don't have direct control
7283 : * of the timestamps, there will be differences on the order of microseconds
7284 : * that can be ignored).
7285 : */
7286 : bool cmp_timestamps;
7287 :
7288 : /**
7289 : * The number of results matching each refund.
7290 : */
7291 : unsigned int *results_matching;
7292 :
7293 : /**
7294 : * The total number of results from the db.
7295 : */
7296 : unsigned int results_length;
7297 : };
7298 :
7299 :
7300 : /**
7301 : * Called after test_lookup_refunds_detailed.
7302 : * @param cls pointer to a TestLookupRefundsDetailed_Closure.
7303 : * @param refund_serial unique serial number of the refund
7304 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
7305 : * @param coin_pub public coin from which the refund comes from
7306 : * @param exchange_url URL of the exchange that issued @a coin_pub
7307 : * @param rtransaction_id identificator of the refund
7308 : * @param reason human-readable explanation of the refund
7309 : * @param refund_amount refund amount which is being taken from @a coin_pub
7310 : * @param pending true if this refund has not been processed by the wallet/exchange
7311 : */
7312 : static void
7313 3 : lookup_refunds_detailed_cb (void *cls,
7314 : uint64_t refund_serial,
7315 : struct GNUNET_TIME_Timestamp timestamp,
7316 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
7317 : const char *exchange_url,
7318 : uint64_t rtransaction_id,
7319 : const char *reason,
7320 : const struct TALER_Amount *refund_amount,
7321 : bool pending)
7322 : {
7323 3 : struct TestLookupRefundsDetailed_Closure *cmp = cls;
7324 3 : if (NULL == cmp)
7325 0 : return;
7326 3 : cmp->results_length += 1;
7327 8 : for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i)
7328 : {
7329 5 : if (((GNUNET_TIME_timestamp_cmp (cmp->refunds_to_cmp[i].timestamp,
7330 : ==,
7331 0 : timestamp)) ||
7332 0 : ! cmp->cmp_timestamps) &&
7333 5 : (0 == GNUNET_memcmp (cmp->refunds_to_cmp[i].coin_pub,
7334 5 : coin_pub)) &&
7335 5 : (0 == strcmp (cmp->refunds_to_cmp[i].exchange_url,
7336 5 : exchange_url)) &&
7337 5 : (0 == strcmp (cmp->refunds_to_cmp[i].reason,
7338 3 : reason)) &&
7339 : (GNUNET_OK ==
7340 3 : TALER_amount_cmp_currency (
7341 3 : &cmp->refunds_to_cmp[i].refund_amount,
7342 3 : refund_amount)) &&
7343 3 : (0 == TALER_amount_cmp (&cmp->refunds_to_cmp[i].refund_amount,
7344 : refund_amount)))
7345 : {
7346 3 : cmp->results_matching[i] += 1;
7347 : }
7348 : }
7349 : }
7350 :
7351 :
7352 : /**
7353 : * Tests looking up refunds with details from the database.
7354 : * @param instance the instance to lookup from.
7355 : * @param h_contract_terms the contract terms the refunds were made for.
7356 : * @param cmp_timestamps whether to compare timestamps or not.
7357 : * @param refunds_length length of @e refunds.
7358 : * @param refunds the refunds we expect to be returned.
7359 : *
7360 : * @return 0 on success, 1 otherwise.
7361 : */
7362 : static int
7363 2 : test_lookup_refunds_detailed (
7364 : const struct InstanceData *instance,
7365 : const struct TALER_PrivateContractHashP *h_contract_terms,
7366 : bool cmp_timestamps,
7367 : unsigned int refunds_length,
7368 : const struct RefundData *refunds)
7369 2 : {
7370 2 : unsigned int results_matching[refunds_length];
7371 2 : struct TestLookupRefundsDetailed_Closure cmp = {
7372 : .refunds_to_cmp_length = refunds_length,
7373 : .refunds_to_cmp = refunds,
7374 : .cmp_timestamps = cmp_timestamps,
7375 : .results_matching = results_matching,
7376 : .results_length = 0
7377 : };
7378 2 : memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
7379 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
7380 2 : if (0 > TALER_MERCHANTDB_iterate_refunds_detailed (pg,
7381 2 : instance->instance.id,
7382 : h_contract_terms,
7383 : &lookup_refunds_detailed_cb,
7384 : &cmp))
7385 : {
7386 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7387 : "Lookup refunds detailed failed\n");
7388 0 : return 1;
7389 : }
7390 2 : if (refunds_length != cmp.results_length)
7391 : {
7392 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7393 : "Lookup refunds detailed failed: incorrect number of results\n")
7394 : ;
7395 0 : return 1;
7396 : }
7397 5 : for (unsigned int i = 0; refunds_length > i; ++i)
7398 : {
7399 3 : if (1 != cmp.results_matching[i])
7400 : {
7401 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7402 : "Lookup refunds detailed failed: mismatched data\n");
7403 0 : return 1;
7404 : }
7405 : }
7406 2 : return 0;
7407 : }
7408 :
7409 :
7410 : /**
7411 : * Closure for get_refund_serial.
7412 : */
7413 : struct LookupRefundSerial_Closure
7414 : {
7415 : /**
7416 : * The refund we are looking up the id for.
7417 : */
7418 : const struct RefundData *refund;
7419 :
7420 : /**
7421 : * The row number found.
7422 : */
7423 : uint64_t serial;
7424 : };
7425 :
7426 :
7427 : /**
7428 : * Called after get_refund_serial.
7429 : * @param cls pointer to a LookupRefundSerial_Closure.
7430 : * @param refund_serial unique serial number of the refund
7431 : * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
7432 : * @param coin_pub public coin from which the refund comes from
7433 : * @param exchange_url URL of the exchange that issued @a coin_pub
7434 : * @param rtransaction_id identificator of the refund
7435 : * @param reason human-readable explanation of the refund
7436 : * @param refund_amount refund amount which is being taken from @a coin_pub
7437 : * @param pending true if this refund has not been processed by the wallet/exchange
7438 : */
7439 : static void
7440 1 : get_refund_serial_cb (void *cls,
7441 : uint64_t refund_serial,
7442 : struct GNUNET_TIME_Timestamp timestamp,
7443 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
7444 : const char *exchange_url,
7445 : uint64_t rtransaction_id,
7446 : const char *reason,
7447 : const struct TALER_Amount *refund_amount,
7448 : bool pending)
7449 : {
7450 1 : struct LookupRefundSerial_Closure *lookup_cls = cls;
7451 1 : if (NULL == lookup_cls)
7452 0 : return;
7453 1 : if ((GNUNET_TIME_timestamp_cmp (lookup_cls->refund->timestamp,
7454 : ==,
7455 1 : timestamp)) &&
7456 1 : (0 == GNUNET_memcmp (lookup_cls->refund->coin_pub,
7457 1 : coin_pub)) &&
7458 1 : (0 == strcmp (lookup_cls->refund->exchange_url,
7459 1 : exchange_url)) &&
7460 1 : (0 == strcmp (lookup_cls->refund->reason,
7461 1 : reason)) &&
7462 : (GNUNET_OK ==
7463 1 : TALER_amount_cmp_currency (
7464 1 : &lookup_cls->refund->refund_amount,
7465 1 : refund_amount)) &&
7466 1 : (0 == TALER_amount_cmp (&lookup_cls->refund->refund_amount,
7467 : refund_amount)))
7468 1 : lookup_cls->serial = refund_serial;
7469 : }
7470 :
7471 :
7472 : /**
7473 : * Utility function for getting the database row number of a refund.
7474 : * @param instance the instance associated with the refund.
7475 : * @param h_contract_terms the contract terms the refund was made with.
7476 : * @param refund the refund we are querying the row number of.
7477 : *
7478 : * @return the row number of the refund.
7479 : */
7480 : static uint64_t
7481 1 : get_refund_serial (const struct InstanceData *instance,
7482 : const struct TALER_PrivateContractHashP *h_contract_terms,
7483 : const struct RefundData *refund)
7484 : {
7485 1 : struct LookupRefundSerial_Closure lookup_cls = {
7486 : .refund = refund,
7487 : .serial = 0
7488 : };
7489 :
7490 1 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
7491 : TALER_MERCHANTDB_set_instance (pg,
7492 : instance->instance.id));
7493 1 : GNUNET_assert (0 < TALER_MERCHANTDB_iterate_refunds_detailed (pg,
7494 : instance->instance.id,
7495 : h_contract_terms,
7496 : &get_refund_serial_cb,
7497 : &lookup_cls));
7498 1 : GNUNET_assert (0 != lookup_cls.serial);
7499 :
7500 1 : return lookup_cls.serial;
7501 : }
7502 :
7503 :
7504 : /**
7505 : * Tests looking up proof of a refund.
7506 : * @param refund_serial the row number of the refund.
7507 : * @param expected_exchange_sig the exchange signature we are expecting.
7508 : * @param expected_exchange_pub the exchange public key we are expecting.
7509 : *
7510 : * @return 0 on success, 1 otherwise.
7511 : */
7512 : static int
7513 1 : test_lookup_refund_proof (uint64_t refund_serial,
7514 : const struct
7515 : TALER_ExchangeSignatureP *expected_exchange_sig,
7516 : const struct
7517 : TALER_ExchangePublicKeyP *expected_exchange_pub)
7518 : {
7519 : struct TALER_ExchangeSignatureP exchange_sig;
7520 : struct TALER_ExchangePublicKeyP exchange_pub;
7521 1 : if (1 != TALER_MERCHANTDB_get_refund_proof (pg,
7522 : refund_serial,
7523 : &exchange_sig,
7524 : &exchange_pub))
7525 : {
7526 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7527 : "Lookup refund proof failed\n");
7528 0 : return 1;
7529 : }
7530 1 : if ((0 != GNUNET_memcmp (expected_exchange_sig,
7531 1 : &exchange_sig)) ||
7532 1 : (0 != GNUNET_memcmp (expected_exchange_pub,
7533 : &exchange_pub)))
7534 : {
7535 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
7536 : "Lookup refund proof failed: mismatched data\n");
7537 0 : return 1;
7538 : }
7539 1 : return 0;
7540 : }
7541 :
7542 :
7543 : /**
7544 : * Closure for testing refund functionality.
7545 : */
7546 : struct TestRefunds_Closure
7547 : {
7548 : /**
7549 : * The instance.
7550 : */
7551 : struct InstanceData instance;
7552 :
7553 : /**
7554 : * The merchant account.
7555 : */
7556 : struct TALER_MERCHANTDB_AccountDetails account;
7557 :
7558 : /**
7559 : * The exchange signing key.
7560 : */
7561 : struct ExchangeSignkeyData signkey;
7562 :
7563 : /**
7564 : * The order data.
7565 : */
7566 : struct OrderData orders[3];
7567 :
7568 : /**
7569 : * The deposit data.
7570 : */
7571 : struct DepositData deposits[5];
7572 :
7573 : /**
7574 : * The refund data.
7575 : */
7576 : struct RefundData refunds[3];
7577 :
7578 : /**
7579 : * The refund proof data.
7580 : */
7581 : struct RefundProofData refund_proof;
7582 : };
7583 :
7584 :
7585 : /**
7586 : * Prepares for testing refunds.
7587 : * @param cls the closure to initialize.
7588 : */
7589 : static void
7590 1 : pre_test_refunds (struct TestRefunds_Closure *cls)
7591 : {
7592 : /* Instance */
7593 1 : make_instance ("test_inst_refunds",
7594 : &cls->instance);
7595 :
7596 : /* Account */
7597 1 : make_account (&cls->account);
7598 1 : cls->account.instance_id = cls->instance.instance.id;
7599 : /* Signing key */
7600 1 : make_exchange_signkey (&cls->signkey);
7601 :
7602 : /* Order */
7603 1 : make_order ("test_refunds_od_0",
7604 : &cls->orders[0]);
7605 1 : make_order ("test_refunds_od_1",
7606 : &cls->orders[1]);
7607 1 : make_order ("test_refunds_od_2",
7608 : &cls->orders[2]);
7609 :
7610 : /* Deposit */
7611 1 : make_deposit (&cls->instance,
7612 1 : &cls->account,
7613 1 : &cls->orders[0],
7614 1 : &cls->signkey,
7615 : &cls->deposits[0]);
7616 1 : make_deposit (&cls->instance,
7617 1 : &cls->account,
7618 1 : &cls->orders[0],
7619 1 : &cls->signkey,
7620 : &cls->deposits[1]);
7621 1 : make_deposit (&cls->instance,
7622 1 : &cls->account,
7623 1 : &cls->orders[1],
7624 1 : &cls->signkey,
7625 : &cls->deposits[2]);
7626 : /* Two deposits of the *same* coin on order 2, via two different
7627 : exchanges (and thus two deposit confirmations). The schema allows
7628 : this: merchant_deposits is unique only on
7629 : (deposit_confirmation_serial, coin_pub). It makes
7630 : do_increase_refund derive the same rtransaction_id twice, which is
7631 : exactly the collision a concurrent refund on another connection
7632 : produces. */
7633 1 : make_deposit (&cls->instance,
7634 1 : &cls->account,
7635 1 : &cls->orders[2],
7636 1 : &cls->signkey,
7637 : &cls->deposits[3]);
7638 1 : make_deposit (&cls->instance,
7639 1 : &cls->account,
7640 1 : &cls->orders[2],
7641 1 : &cls->signkey,
7642 : &cls->deposits[4]);
7643 1 : cls->deposits[4].exchange_url = "https://test-exchange-two/";
7644 1 : cls->deposits[4].coin_pub = cls->deposits[3].coin_pub;
7645 1 : cls->deposits[4].coin_sig = cls->deposits[3].coin_sig;
7646 :
7647 : /* Refund */
7648 1 : make_refund (&cls->deposits[0],
7649 : &cls->refunds[0]);
7650 1 : make_refund (&cls->deposits[2],
7651 : &cls->refunds[1]);
7652 1 : make_refund (&cls->deposits[2],
7653 : &cls->refunds[2]);
7654 1 : GNUNET_assert (GNUNET_OK ==
7655 : TALER_string_to_amount ("EUR:10.00",
7656 : &cls->refunds[1].refund_amount));
7657 1 : cls->refunds[1].reason = "refund 1";
7658 1 : GNUNET_assert (GNUNET_OK ==
7659 : TALER_string_to_amount ("EUR:10.00",
7660 : &cls->refunds[2].refund_amount));
7661 1 : cls->refunds[2].reason = "refund 2";
7662 :
7663 : /* Refund proof */
7664 1 : GNUNET_assert (GNUNET_OK ==
7665 : TALER_string_to_amount ("EUR:0.02",
7666 : &cls->refund_proof.refund_fee));
7667 1 : memset (&cls->refund_proof.exchange_sig,
7668 : 42,
7669 : sizeof (cls->refund_proof.exchange_sig));
7670 1 : }
7671 :
7672 :
7673 : /**
7674 : * Cleans up after testing refunds.
7675 : * @param cls the closure.
7676 : */
7677 : static void
7678 1 : post_test_refunds (struct TestRefunds_Closure *cls)
7679 : {
7680 1 : free_instance_data (&cls->instance);
7681 1 : free_order_data (&cls->orders[0]);
7682 1 : free_order_data (&cls->orders[1]);
7683 1 : free_order_data (&cls->orders[2]);
7684 1 : }
7685 :
7686 :
7687 : /**
7688 : * Runs the refund tests.
7689 : * @param cls the closure.
7690 : *
7691 : * @return 0 on success, 1 otherwise.
7692 : */
7693 : static int
7694 1 : run_test_refunds (struct TestRefunds_Closure *cls)
7695 : {
7696 : struct TALER_Amount inc;
7697 : uint64_t refund_serial;
7698 :
7699 : /* Insert an instance */
7700 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
7701 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7702 : /* Insert an account */
7703 1 : TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
7704 : &cls->account,
7705 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7706 : /* Insert an order */
7707 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
7708 : &cls->orders[0],
7709 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7710 : /* Insert contract terms */
7711 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
7712 : &cls->orders[0],
7713 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7714 : /* Insert a signing key */
7715 1 : TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
7716 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7717 : /* Insert a deposit */
7718 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
7719 : &cls->signkey,
7720 : &cls->deposits[0],
7721 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7722 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
7723 : &cls->signkey,
7724 : &cls->deposits[1],
7725 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7726 : /* Mark as paid */
7727 1 : TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
7728 : &cls->orders[0],
7729 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7730 : /* Test refund coin */
7731 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
7732 : TALER_MERCHANTDB_insert_refund_by_coin (pg,
7733 : cls->instance.instance.id,
7734 : &cls->deposits[0].h_contract_terms
7735 : ,
7736 : cls->refunds[0].timestamp,
7737 : cls->refunds[0].coin_pub,
7738 : cls->refunds[0].reason),
7739 : "Refund coin failed\n");
7740 1 : refund_serial = get_refund_serial (&cls->instance,
7741 1 : &cls->deposits[0].h_contract_terms,
7742 1 : &cls->refunds[0]);
7743 : /* Test double refund fails */
7744 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
7745 : TALER_MERCHANTDB_insert_refund_by_coin (pg,
7746 : cls->instance.instance.id,
7747 : &cls->deposits[0].h_contract_terms
7748 : ,
7749 : cls->refunds[0].timestamp,
7750 : cls->refunds[0].coin_pub,
7751 : cls->refunds[0].reason),
7752 : "Refund coin failed\n");
7753 : /* Test lookup refunds */
7754 1 : TEST_RET_ON_FAIL (test_lookup_refunds (&cls->instance,
7755 : &cls->deposits[0].h_contract_terms,
7756 : 1,
7757 : cls->refunds[0].coin_pub,
7758 : &cls->refunds[0].refund_amount));
7759 : /* Test lookup refunds detailed */
7760 1 : TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance,
7761 : &cls->deposits[0].
7762 : h_contract_terms,
7763 : true,
7764 : 1,
7765 : &cls->refunds[0]));
7766 : /* Test insert refund proof */
7767 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
7768 : TALER_MERCHANTDB_insert_refund_proof (pg,
7769 : refund_serial,
7770 : &cls->refund_proof.
7771 : exchange_sig,
7772 : &cls->signkey.exchange_pub
7773 : ),
7774 : "Insert refund proof failed\n");
7775 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
7776 : TALER_MERCHANTDB_insert_refund_proof (pg,
7777 : refund_serial,
7778 : &cls->refund_proof.
7779 : exchange_sig,
7780 : &cls->signkey.exchange_pub
7781 : ),
7782 : "Insert refund proof failed\n");
7783 : /* Test that we can't give too much in refunds */
7784 1 : GNUNET_assert (GNUNET_OK ==
7785 : TALER_string_to_amount ("EUR:1000.00",
7786 : &inc));
7787 1 : TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_TOO_HIGH ==
7788 : TALER_MERCHANTDB_do_increase_refund (pg,
7789 : cls->instance.instance.id,
7790 : cls->orders[0].id,
7791 : &inc,
7792 : NULL,
7793 : NULL,
7794 : "more"),
7795 : "Increase refund failed\n");
7796 : /* Test increase refund */
7797 1 : GNUNET_assert (GNUNET_OK ==
7798 : TALER_string_to_amount ("EUR:1.00",
7799 : &inc));
7800 1 : TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
7801 : TALER_MERCHANTDB_do_increase_refund (pg,
7802 : cls->instance.instance.id,
7803 : cls->orders[0].id,
7804 : &inc,
7805 : NULL,
7806 : NULL,
7807 : "more"),
7808 : "Increase refund failed\n");
7809 : /* Test lookup refund proof */
7810 1 : TEST_RET_ON_FAIL (test_lookup_refund_proof (1,
7811 : &cls->refund_proof.exchange_sig,
7812 : &cls->signkey.exchange_pub));
7813 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
7814 : &cls->orders[1],
7815 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7816 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
7817 : &cls->orders[1],
7818 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7819 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
7820 : &cls->signkey,
7821 : &cls->deposits[2],
7822 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7823 1 : TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
7824 : &cls->orders[1],
7825 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7826 : /* Test refunding a small amount of the coin, then increasing it */
7827 1 : GNUNET_assert (GNUNET_OK ==
7828 : TALER_string_to_amount ("EUR:10.00",
7829 : &inc));
7830 1 : TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
7831 : TALER_MERCHANTDB_do_increase_refund (pg,
7832 : cls->instance.instance.id,
7833 : cls->orders[1].id,
7834 : &inc,
7835 : NULL,
7836 : NULL,
7837 : cls->refunds[1].reason),
7838 : "Increase refund failed\n");
7839 1 : GNUNET_assert (GNUNET_OK ==
7840 : TALER_string_to_amount ("EUR:20.00",
7841 : &inc));
7842 1 : TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
7843 : TALER_MERCHANTDB_do_increase_refund (pg,
7844 : cls->instance.instance.id,
7845 : cls->orders[1].id,
7846 : &inc,
7847 : NULL,
7848 : NULL,
7849 : cls->refunds[2].reason),
7850 : "Increase refund failed\n");
7851 1 : TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance,
7852 : &cls->deposits[2].
7853 : h_contract_terms,
7854 : false,
7855 : 2,
7856 : &cls->refunds[1]));
7857 : /* Two deposits of the same coin on the same order make
7858 : do_increase_refund pick the same rtransaction_id twice, which is
7859 : exactly what a concurrent refund on another connection does. The
7860 : resulting unique violation must NOT be reported as
7861 : #TALER_MERCHANTDB_RS_NO_SUCH_ORDER (a bogus 404 for an order that
7862 : obviously exists), but as a serialization failure the caller can
7863 : retry. */
7864 1 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
7865 : &cls->orders[2],
7866 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7867 1 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
7868 : &cls->orders[2],
7869 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7870 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
7871 : &cls->signkey,
7872 : &cls->deposits[3],
7873 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7874 1 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
7875 : &cls->signkey,
7876 : &cls->deposits[4],
7877 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7878 1 : TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
7879 : &cls->orders[2],
7880 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
7881 : /* More than a single deposit is worth, so that the second (duplicate)
7882 : deposit has to be refunded as well. */
7883 1 : GNUNET_assert (GNUNET_OK ==
7884 : TALER_string_to_amount ("EUR:60.00",
7885 : &inc));
7886 1 : TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SOFT_ERROR ==
7887 : TALER_MERCHANTDB_do_increase_refund (pg,
7888 : cls->instance.instance.id,
7889 : cls->orders[2].id,
7890 : &inc,
7891 : NULL,
7892 : NULL,
7893 : "duplicate coin"),
7894 : "Duplicate rtransaction_id not reported as serialization failure\n");
7895 1 : return 0;
7896 : }
7897 :
7898 :
7899 : /**
7900 : * All logic for testing refunds.
7901 : *
7902 : * @return 0 on success, 1 otherwise.
7903 : */
7904 : static int
7905 1 : test_refunds (void)
7906 : {
7907 : struct TestRefunds_Closure test_cls;
7908 : int test_result;
7909 :
7910 1 : pre_test_refunds (&test_cls);
7911 1 : test_result = run_test_refunds (&test_cls);
7912 1 : post_test_refunds (&test_cls);
7913 1 : return test_result;
7914 : }
7915 :
7916 :
7917 : /**
7918 : * Convenience function that reverses an array of orders.
7919 : * @param orders_length length of @e orders.
7920 : * @param orders the array to reverse.
7921 : */
7922 : static void
7923 27 : reverse_order_data_array (unsigned int orders_length,
7924 : struct OrderData *orders)
7925 27 : {
7926 27 : struct OrderData tmp[orders_length];
7927 539 : for (unsigned int i = 0; i < orders_length; ++i)
7928 512 : tmp[i] = orders[orders_length - 1 - i];
7929 539 : for (unsigned int i = 0; i < orders_length; ++i)
7930 512 : orders[i] = tmp[i];
7931 27 : }
7932 :
7933 :
7934 : /**
7935 : * Closure for testing all the filters for looking up orders.
7936 : */
7937 : struct TestLookupOrdersAllFilters_Closure
7938 : {
7939 : /**
7940 : * The instance.
7941 : */
7942 : struct InstanceData instance;
7943 :
7944 : /**
7945 : * The merchant account.
7946 : */
7947 : struct TALER_MERCHANTDB_AccountDetails account;
7948 :
7949 : /**
7950 : * The exchange signing key.
7951 : */
7952 : struct ExchangeSignkeyData signkey;
7953 :
7954 : /**
7955 : * The array of order ids.
7956 : */
7957 : char *order_ids[64];
7958 :
7959 : /**
7960 : * The array of orders.
7961 : */
7962 : struct OrderData orders[64];
7963 :
7964 : /**
7965 : * The array of deposits.
7966 : */
7967 : struct DepositData deposits[64];
7968 :
7969 : /**
7970 : * The array of refunds.
7971 : */
7972 : struct RefundData refunds[64];
7973 : };
7974 :
7975 :
7976 : /**
7977 : * Sets up for testing lookup order filters.
7978 : * @param cls the closure.
7979 : */
7980 : static void
7981 1 : pre_test_lookup_orders_all_filters (
7982 : struct TestLookupOrdersAllFilters_Closure *cls)
7983 : {
7984 1 : make_instance ("test_inst_lookup_orders_all_filters",
7985 : &cls->instance);
7986 1 : make_account (&cls->account);
7987 1 : cls->account.instance_id = cls->instance.instance.id;
7988 1 : make_exchange_signkey (&cls->signkey);
7989 65 : for (unsigned int i = 0; i < 64; ++i)
7990 : {
7991 64 : (void) GNUNET_asprintf (&cls->order_ids[i],
7992 : "test_orders_filter_od_%u",
7993 : i);
7994 64 : make_order (cls->order_ids[i],
7995 : &cls->orders[i]);
7996 64 : GNUNET_assert (0 ==
7997 : json_object_set_new (cls->orders[i].contract,
7998 : "order_id",
7999 : json_string (cls->order_ids[i])));
8000 64 : make_deposit (&cls->instance,
8001 64 : &cls->account,
8002 64 : &cls->orders[i],
8003 64 : &cls->signkey,
8004 : &cls->deposits[i]);
8005 64 : make_refund (&cls->deposits[i],
8006 : &cls->refunds[i]);
8007 : }
8008 1 : }
8009 :
8010 :
8011 : /**
8012 : * Cleans up after testing lookup order filters.
8013 : * @param cls the closure.
8014 : */
8015 : static void
8016 1 : post_test_lookup_orders_all_filters (
8017 : struct TestLookupOrdersAllFilters_Closure *cls)
8018 : {
8019 1 : free_instance_data (&cls->instance);
8020 65 : for (unsigned int i = 0; i < 64; ++i)
8021 : {
8022 64 : free_order_data (&cls->orders[i]);
8023 64 : GNUNET_free (cls->order_ids[i]);
8024 : }
8025 1 : }
8026 :
8027 :
8028 : /**
8029 : * Runs the tests for lookup order filters.
8030 : * @param cls the closure.
8031 : *
8032 : * @return 0 on success, 1 otherwise.
8033 : */
8034 : static int
8035 1 : run_test_lookup_orders_all_filters (
8036 : struct TestLookupOrdersAllFilters_Closure *cls)
8037 : {
8038 : /* Order filter extravaganza */
8039 : struct
8040 : {
8041 : bool claimed;
8042 : bool paid;
8043 : bool refunded;
8044 : bool wired;
8045 : } order_status[64];
8046 : unsigned int *permutation;
8047 :
8048 : /* Pseudorandomly generate variations for the filter to differentiate */
8049 1 : permutation = GNUNET_CRYPTO_random_permute (64);
8050 65 : for (unsigned int i = 0; i < 64; ++i)
8051 : {
8052 64 : unsigned int dest = permutation[i];
8053 64 : order_status[dest].claimed = (i & 1) ? true : false;
8054 64 : order_status[dest].paid = (3 == (i & 3)) ? true : false;
8055 64 : order_status[dest].refunded = (5 == (i & 5)) ? true : false;
8056 64 : order_status[dest].wired = (9 == (i & 9)) ? true : false;
8057 : }
8058 1 : GNUNET_free (permutation);
8059 :
8060 :
8061 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
8062 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8063 1 : TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
8064 : &cls->account,
8065 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8066 1 : TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
8067 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8068 65 : for (unsigned int i = 0; i < 64; ++i)
8069 : {
8070 : uint64_t order_serial;
8071 :
8072 64 : TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
8073 : &cls->orders[i],
8074 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8075 64 : order_serial = get_order_serial (&cls->instance,
8076 64 : &cls->orders[i]);
8077 :
8078 64 : if (order_status[i].claimed)
8079 : {
8080 32 : TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
8081 : &cls->orders[i],
8082 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8083 : }
8084 : else
8085 : {
8086 32 : continue;
8087 : }
8088 :
8089 32 : if (order_status[i].paid)
8090 16 : TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
8091 : &cls->orders[i],
8092 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8093 32 : if (order_status[i].refunded)
8094 : {
8095 16 : TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
8096 : &cls->signkey,
8097 : &cls->deposits[i],
8098 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8099 16 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
8100 : TALER_MERCHANTDB_insert_refund_by_coin (pg,
8101 : cls->instance.instance.id,
8102 : &cls->deposits[i].
8103 : h_contract_terms,
8104 : cls->refunds[i].timestamp,
8105 : cls->refunds[i].coin_pub,
8106 : cls->refunds[i].reason),
8107 : "Refund coin failed\n");
8108 : }
8109 :
8110 32 : if (order_status[i].wired)
8111 16 : TEST_RET_ON_FAIL (test_mark_order_wired (order_serial,
8112 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8113 : }
8114 :
8115 : /* There are 3^3 = 27 possibilities here, not counting inc/dec and start row */
8116 28 : for (unsigned int i = 0; i < 27; ++i)
8117 : {
8118 27 : struct TALER_MERCHANTDB_OrderFilter filter = {
8119 27 : .paid = (i % 3) + 1,
8120 27 : .refunded = ((i / 3) % 3) + 1,
8121 27 : .wired = ((i / 9) % 3) + 1,
8122 : .expired = TALER_EXCHANGE_YNA_ALL,
8123 : .date = GNUNET_TIME_UNIT_ZERO_TS,
8124 : .start_row = 0,
8125 : .delta = 64
8126 : };
8127 27 : unsigned int orders_length = 0;
8128 : struct OrderData orders[64];
8129 :
8130 : /* Now figure out which orders should make it through the filter */
8131 1755 : for (unsigned int j = 0; j < 64; ++j)
8132 : {
8133 1728 : if (((TALER_EXCHANGE_YNA_YES == filter.paid) &&
8134 576 : (true != order_status[j].paid)) ||
8135 1296 : ((TALER_EXCHANGE_YNA_NO == filter.paid) &&
8136 576 : (false != order_status[j].paid)) ||
8137 1152 : ((TALER_EXCHANGE_YNA_YES == filter.refunded) &&
8138 384 : (true != order_status[j].refunded)) ||
8139 864 : ((TALER_EXCHANGE_YNA_NO == filter.refunded) &&
8140 384 : (false != order_status[j].refunded)) ||
8141 768 : ((TALER_EXCHANGE_YNA_YES == filter.wired) &&
8142 256 : (true != order_status[j].wired)) ||
8143 576 : ((TALER_EXCHANGE_YNA_NO == filter.wired) &&
8144 256 : (false != order_status[j].wired)))
8145 1216 : continue;
8146 512 : orders[orders_length] = cls->orders[j];
8147 512 : orders_length += 1;
8148 : }
8149 :
8150 : /* Test the lookup */
8151 27 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
8152 : &filter,
8153 : orders_length,
8154 : orders));
8155 :
8156 : /* Now test decreasing */
8157 27 : filter.start_row = 256;
8158 27 : filter.date = GNUNET_TIME_UNIT_FOREVER_TS;
8159 27 : filter.delta = -64;
8160 :
8161 27 : reverse_order_data_array (orders_length,
8162 : orders);
8163 :
8164 27 : TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
8165 : &filter,
8166 : orders_length,
8167 : orders));
8168 : }
8169 :
8170 1 : return 0;
8171 : }
8172 :
8173 :
8174 : /**
8175 : * Handles all logic for testing lookup order filters.
8176 : *
8177 : * @return 0 on success, 1 otherwise.
8178 : */
8179 : static int
8180 1 : test_lookup_orders_all_filters (void)
8181 : {
8182 : struct TestLookupOrdersAllFilters_Closure test_cls;
8183 : int test_result;
8184 :
8185 1 : memset (&test_cls,
8186 : 0,
8187 : sizeof (test_cls));
8188 1 : pre_test_lookup_orders_all_filters (&test_cls);
8189 1 : test_result = run_test_lookup_orders_all_filters (&test_cls);
8190 1 : post_test_lookup_orders_all_filters (&test_cls);
8191 1 : return test_result;
8192 : }
8193 :
8194 :
8195 : static void
8196 3 : kyc_status_ok (
8197 : void *cls,
8198 : const struct TALER_MerchantWireHashP *h_wire,
8199 : struct TALER_FullPayto payto_uri,
8200 : const char *exchange_url,
8201 : struct GNUNET_TIME_Timestamp last_check,
8202 : bool kyc_ok,
8203 : const struct TALER_AccountAccessTokenP *access_token,
8204 : unsigned int last_http_status,
8205 : enum TALER_ErrorCode last_ec,
8206 : bool in_aml_review,
8207 : const json_t *jlimits)
8208 : {
8209 3 : bool *fail = cls;
8210 :
8211 3 : if (kyc_ok)
8212 2 : *fail = false;
8213 3 : }
8214 :
8215 :
8216 : static void
8217 3 : kyc_status_fail (
8218 : void *cls,
8219 : const struct TALER_MerchantWireHashP *h_wire,
8220 : struct TALER_FullPayto payto_uri,
8221 : const char *exchange_url,
8222 : struct GNUNET_TIME_Timestamp last_check,
8223 : bool kyc_ok,
8224 : const struct TALER_AccountAccessTokenP *access_token,
8225 : unsigned int last_http_status,
8226 : enum TALER_ErrorCode last_ec,
8227 : bool in_aml_review,
8228 : const json_t *jlimits)
8229 : {
8230 3 : bool *fail = cls;
8231 :
8232 3 : if (! kyc_ok)
8233 2 : *fail = false;
8234 3 : }
8235 :
8236 :
8237 : /**
8238 : * Expected result for a KYC failure lookup.
8239 : */
8240 : struct KycFailureExpected
8241 : {
8242 : /**
8243 : * Account the result must belong to.
8244 : */
8245 : const struct TALER_MERCHANTDB_AccountDetails *account;
8246 :
8247 : /**
8248 : * Exchange the result must belong to.
8249 : */
8250 : const char *exchange_url;
8251 :
8252 : /**
8253 : * Expected time of the failed KYC check.
8254 : */
8255 : struct GNUNET_TIME_Timestamp timestamp;
8256 :
8257 : /**
8258 : * Expected HTTP status returned by the exchange.
8259 : */
8260 : unsigned int exchange_http_status;
8261 :
8262 : /**
8263 : * Number of results received.
8264 : */
8265 : unsigned int called;
8266 :
8267 : /**
8268 : * Set if a result did not match expectations.
8269 : */
8270 : bool failed;
8271 : };
8272 :
8273 :
8274 : /**
8275 : * Check a KYC failure record returned by iterate_kyc_statuses().
8276 : *
8277 : * @param cls a `struct KycFailureExpected *`
8278 : * @param h_wire hash of the wire account
8279 : * @param payto_uri payto URI of the merchant account
8280 : * @param exchange_url exchange the status applies to
8281 : * @param last_check time of the last KYC check
8282 : * @param kyc_ok true if KYC was successful
8283 : * @param access_token access token, if available
8284 : * @param last_http_status HTTP status of the last check
8285 : * @param last_ec Taler error code of the last check
8286 : * @param in_aml_review true if the account is under AML review
8287 : * @param jlimits account limits, if known
8288 : */
8289 : static void
8290 2 : kyc_failure_check (
8291 : void *cls,
8292 : const struct TALER_MerchantWireHashP *h_wire,
8293 : struct TALER_FullPayto payto_uri,
8294 : const char *exchange_url,
8295 : struct GNUNET_TIME_Timestamp last_check,
8296 : bool kyc_ok,
8297 : const struct TALER_AccountAccessTokenP *access_token,
8298 : unsigned int last_http_status,
8299 : enum TALER_ErrorCode last_ec,
8300 : bool in_aml_review,
8301 : const json_t *jlimits)
8302 : {
8303 2 : struct KycFailureExpected *expected = cls;
8304 :
8305 2 : expected->called++;
8306 2 : if ( (0 != GNUNET_memcmp (&expected->account->h_wire,
8307 2 : h_wire)) ||
8308 2 : (0 != strcmp (expected->account->payto_uri.full_payto,
8309 2 : payto_uri.full_payto)) ||
8310 2 : (0 != strcmp (expected->exchange_url,
8311 2 : exchange_url)) ||
8312 2 : (GNUNET_TIME_timestamp_cmp (expected->timestamp,
8313 : !=,
8314 2 : last_check)) ||
8315 2 : kyc_ok ||
8316 2 : (NULL != access_token) ||
8317 2 : (expected->exchange_http_status != last_http_status) ||
8318 2 : (TALER_EC_NONE != last_ec) ||
8319 2 : in_aml_review ||
8320 : (NULL != jlimits) )
8321 : {
8322 0 : GNUNET_break (0);
8323 0 : expected->failed = true;
8324 : }
8325 2 : }
8326 :
8327 :
8328 : /**
8329 : * Count a database notification.
8330 : *
8331 : * @param cls an `unsigned int *`
8332 : * @param extra unused notification payload
8333 : * @param extra_size size of @a extra
8334 : */
8335 : static void
8336 4 : kyc_event_cb (void *cls,
8337 : const void *extra,
8338 : size_t extra_size)
8339 : {
8340 4 : unsigned int *fired = cls;
8341 :
8342 : (void) extra;
8343 : (void) extra_size;
8344 4 : (*fired)++;
8345 4 : }
8346 :
8347 :
8348 : /**
8349 : * Test inserting and updating a failed KYC check.
8350 : * Regression test for #10761, which reported that the operation formerly
8351 : * named account_kyc_set_failed had no test coverage.
8352 : *
8353 : * @param instance instance that owns @a account
8354 : * @param account merchant account to use
8355 : * @return 0 on success, 1 otherwise
8356 : */
8357 : static int
8358 1 : test_insert_kyc_failure (
8359 : const struct InstanceData *instance,
8360 : const struct TALER_MERCHANTDB_AccountDetails *account)
8361 : {
8362 : static const char exchange_url[] = "https://exchange2.com/";
8363 1 : struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP account_event = {
8364 1 : .header.size = htons (sizeof (account_event)),
8365 1 : .header.type = htons (
8366 : TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED),
8367 : .h_wire = account->h_wire
8368 : };
8369 1 : struct GNUNET_DB_EventHeaderP general_event = {
8370 1 : .size = htons (sizeof (general_event)),
8371 1 : .type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED)
8372 : };
8373 1 : struct GNUNET_DB_EventHandler *account_eh = NULL;
8374 1 : struct GNUNET_DB_EventHandler *general_eh = NULL;
8375 1 : unsigned int account_events = 0;
8376 1 : unsigned int general_events = 0;
8377 1 : struct KycFailureExpected expected = {
8378 : .account = account,
8379 : .exchange_url = exchange_url,
8380 : .exchange_http_status = MHD_HTTP_BAD_GATEWAY
8381 : };
8382 1 : int ret = 1;
8383 :
8384 1 : TEST_SET_INSTANCE (instance->instance.id,
8385 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
8386 1 : account_eh = TALER_MERCHANTDB_event_listen (
8387 : pg,
8388 : &account_event.header,
8389 1 : GNUNET_TIME_UNIT_FOREVER_REL,
8390 : &kyc_event_cb,
8391 : &account_events);
8392 1 : if (NULL == account_eh)
8393 : {
8394 0 : GNUNET_break (0);
8395 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8396 : "Failed to listen for account KYC events\n");
8397 0 : goto cleanup;
8398 : }
8399 1 : general_eh = TALER_MERCHANTDB_event_listen (
8400 : pg,
8401 : &general_event,
8402 1 : GNUNET_TIME_UNIT_FOREVER_REL,
8403 : &kyc_event_cb,
8404 : &general_events);
8405 1 : if (NULL == general_eh)
8406 : {
8407 0 : GNUNET_break (0);
8408 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8409 : "Failed to listen for general KYC events\n");
8410 0 : goto cleanup;
8411 : }
8412 :
8413 1 : expected.timestamp = GNUNET_TIME_timestamp_get ();
8414 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8415 1 : TALER_MERCHANTDB_insert_kyc_failure (
8416 : pg,
8417 1 : instance->instance.id,
8418 : &account->h_wire,
8419 : exchange_url,
8420 : expected.timestamp,
8421 : expected.exchange_http_status,
8422 : false))
8423 : {
8424 0 : GNUNET_break (0);
8425 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8426 : "Inserting KYC failure failed\n");
8427 0 : goto cleanup;
8428 : }
8429 1 : GNUNET_PQ_event_do_poll (pg->conn);
8430 1 : if ( (1 != account_events) ||
8431 1 : (1 != general_events) )
8432 : {
8433 0 : GNUNET_break (0);
8434 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8435 : "Inserting KYC failure generated %u account and %u general"
8436 : " events, expected one each\n",
8437 : account_events,
8438 : general_events);
8439 0 : goto cleanup;
8440 : }
8441 1 : if (1 != TALER_MERCHANTDB_iterate_kyc_statuses (
8442 : pg,
8443 1 : instance->instance.id,
8444 : &account->h_wire,
8445 : exchange_url,
8446 : &kyc_failure_check,
8447 : &expected))
8448 : {
8449 0 : GNUNET_break (0);
8450 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8451 : "Looking up inserted KYC failure failed\n");
8452 0 : goto cleanup;
8453 : }
8454 1 : if (expected.failed ||
8455 1 : (1 != expected.called))
8456 : {
8457 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8458 : "Inserted KYC failure did not match expectations\n");
8459 0 : goto cleanup;
8460 : }
8461 :
8462 1 : expected.timestamp = GNUNET_TIME_timestamp_get ();
8463 1 : expected.exchange_http_status = MHD_HTTP_ACCEPTED;
8464 1 : expected.called = 0;
8465 1 : expected.failed = false;
8466 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8467 1 : TALER_MERCHANTDB_insert_kyc_failure (
8468 : pg,
8469 1 : instance->instance.id,
8470 : &account->h_wire,
8471 : exchange_url,
8472 : expected.timestamp,
8473 : expected.exchange_http_status,
8474 : false))
8475 : {
8476 0 : GNUNET_break (0);
8477 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8478 : "Updating KYC failure failed\n");
8479 0 : goto cleanup;
8480 : }
8481 1 : GNUNET_PQ_event_do_poll (pg->conn);
8482 1 : if ( (2 != account_events) ||
8483 1 : (2 != general_events) )
8484 : {
8485 0 : GNUNET_break (0);
8486 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8487 : "Updating KYC failure generated %u account and %u general"
8488 : " events, expected two each\n",
8489 : account_events,
8490 : general_events);
8491 0 : goto cleanup;
8492 : }
8493 1 : if (1 != TALER_MERCHANTDB_iterate_kyc_statuses (
8494 : pg,
8495 1 : instance->instance.id,
8496 : &account->h_wire,
8497 : exchange_url,
8498 : &kyc_failure_check,
8499 : &expected))
8500 : {
8501 0 : GNUNET_break (0);
8502 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8503 : "Looking up updated KYC failure failed\n");
8504 0 : goto cleanup;
8505 : }
8506 1 : if (expected.failed ||
8507 1 : (1 != expected.called))
8508 : {
8509 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8510 : "Updated KYC failure did not match expectations\n");
8511 0 : goto cleanup;
8512 : }
8513 1 : ret = 0;
8514 :
8515 1 : cleanup:
8516 1 : if (NULL != general_eh)
8517 1 : TALER_MERCHANTDB_event_listen_cancel (general_eh);
8518 1 : if (NULL != account_eh)
8519 1 : TALER_MERCHANTDB_event_listen_cancel (account_eh);
8520 1 : return ret;
8521 : }
8522 :
8523 :
8524 : /**
8525 : * Function that tests the KYC table.
8526 : *
8527 : * @return 0 on success, 1 otherwise.
8528 : */
8529 : static int
8530 1 : test_kyc (void)
8531 : {
8532 : struct InstanceData instance;
8533 : struct TALER_MERCHANTDB_AccountDetails account;
8534 : bool fail;
8535 : struct GNUNET_TIME_Timestamp now;
8536 : uint64_t alerts;
8537 :
8538 1 : make_instance ("test_kyc",
8539 : &instance);
8540 1 : make_account (&account);
8541 1 : account.instance_id = instance.instance.id;
8542 1 : TEST_RET_ON_FAIL (test_insert_instance (&instance,
8543 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8544 1 : TEST_RET_ON_FAIL (test_insert_account (&instance,
8545 : &account,
8546 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8547 : /* Arm the KYC alert path: merchant_send_kyc_notification() returns
8548 : early unless the instance has BOTH an e-mail address and a
8549 : notification language. No C entry point writes
8550 : 'notification_language', hence the direct UPDATE. */
8551 1 : TEST_RET_ON_FAIL (exec_sql ("UPDATE merchant.merchant_instances"
8552 : " SET email='kyc-alerts@example.com'"
8553 : " ,notification_language='en'"
8554 : " WHERE merchant_id='test_kyc'"));
8555 1 : now = GNUNET_TIME_timestamp_get ();
8556 1 : TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8557 : TALER_MERCHANTDB_insert_kyc_status (pg,
8558 : instance.instance.id,
8559 : &account.h_wire,
8560 : "https://exchange.net/",
8561 : now,
8562 : GNUNET_TIME_UNIT_FOREVER_ABS,
8563 : GNUNET_TIME_UNIT_HOURS,
8564 : MHD_HTTP_OK,
8565 : TALER_EC_NONE,
8566 : 42,
8567 : NULL,
8568 : NULL,
8569 : false,
8570 : false));
8571 : /* The AFTER INSERT trigger must have queued exactly one alert.
8572 : Regression test: merchant_send_kyc_notification() used to call
8573 : random_bytea()/uri_escape()/base32_crockford() unqualified -- they
8574 : live in schema 'merchant' only, while the search_path holds just the
8575 : per-instance schema -- and to insert a 'merchant_serial' column that
8576 : merchant-0036 dropped. Either turned this call into a HARD_ERROR,
8577 : which taler-merchant-kyccheck treats as fatal. */
8578 1 : TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num"
8579 : " FROM merchant_reports"
8580 : " WHERE one_shot_hidden",
8581 : &alerts));
8582 1 : TEST_COND_RET_ON_FAIL (1 == alerts,
8583 : "KYC status change did not queue an alert report\n");
8584 1 : TEST_RET_ON_FAIL (test_insert_kyc_failure (&instance,
8585 : &account));
8586 1 : TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8587 : TALER_MERCHANTDB_insert_kyc_status (pg,
8588 : instance.instance.id,
8589 : &account.h_wire,
8590 : "https://exchange.net/",
8591 : now,
8592 : GNUNET_TIME_UNIT_FOREVER_ABS,
8593 : GNUNET_TIME_UNIT_HOURS,
8594 : MHD_HTTP_OK,
8595 : TALER_EC_NONE,
8596 : 42,
8597 : NULL,
8598 : NULL,
8599 : false,
8600 : true));
8601 1 : fail = true;
8602 1 : TEST_RET_ON_FAIL (1 !=
8603 : TALER_MERCHANTDB_iterate_kyc_statuses (pg,
8604 : instance.instance.id,
8605 : &account.h_wire,
8606 : "https://exchange.net/",
8607 : &kyc_status_ok,
8608 : &fail));
8609 1 : TEST_RET_ON_FAIL (fail);
8610 1 : fail = true;
8611 1 : TEST_RET_ON_FAIL (1 !=
8612 : TALER_MERCHANTDB_iterate_kyc_statuses (pg,
8613 : instance.instance.id,
8614 : NULL,
8615 : "https://exchange2.com/",
8616 : &kyc_status_fail,
8617 : &fail));
8618 1 : TEST_RET_ON_FAIL (fail);
8619 1 : fail = true;
8620 1 : TEST_RET_ON_FAIL (2 !=
8621 : TALER_MERCHANTDB_iterate_kyc_statuses (pg,
8622 : instance.instance.id,
8623 : NULL,
8624 : NULL,
8625 : &kyc_status_fail,
8626 : &fail));
8627 1 : TEST_RET_ON_FAIL (fail);
8628 1 : fail = true;
8629 1 : TEST_RET_ON_FAIL (2 !=
8630 : TALER_MERCHANTDB_iterate_kyc_statuses (pg,
8631 : instance.instance.id,
8632 : NULL,
8633 : NULL,
8634 : &kyc_status_ok,
8635 : &fail));
8636 1 : TEST_RET_ON_FAIL (fail);
8637 : /* A bookkeeping-only re-poll -- kyc_ok, last_rule_gen, aml_review and
8638 : jaccount_limits are unchanged, only the timestamps move -- must NOT
8639 : queue another alert. Regression test: pg_triggers.sql used to
8640 : install a second, unguarded merchant_kyc_update_trigger() on top of
8641 : the guarded one in pg_merchant_kyc_trigger.sql, and being loaded
8642 : last it won, so every next_kyc_poll update alerted the merchant. */
8643 1 : TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num"
8644 : " FROM merchant_reports"
8645 : " WHERE report_description"
8646 : " ='automatically triggered KYC alert'",
8647 : &alerts));
8648 1 : TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8649 : TALER_MERCHANTDB_insert_kyc_status (pg,
8650 : instance.instance.id,
8651 : &account.h_wire,
8652 : "https://exchange.net/",
8653 : GNUNET_TIME_timestamp_get (),
8654 : GNUNET_TIME_UNIT_FOREVER_ABS,
8655 : GNUNET_TIME_UNIT_MINUTES,
8656 : MHD_HTTP_OK,
8657 : TALER_EC_NONE,
8658 : 42,
8659 : NULL,
8660 : NULL,
8661 : false,
8662 : true));
8663 : {
8664 : uint64_t alerts2;
8665 :
8666 1 : TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num"
8667 : " FROM merchant_reports"
8668 : " WHERE report_description"
8669 : " ='automatically triggered KYC alert'",
8670 : &alerts2));
8671 1 : TEST_COND_RET_ON_FAIL (alerts == alerts2,
8672 : "Bookkeeping-only KYC update queued an alert\n");
8673 : }
8674 : /* Adding an account must queue an account-change alert. Regression
8675 : test: pg_merchant_account_trigger.sql used to (re)define
8676 : 'merchant_kyc_update_trigger' rather than
8677 : 'merchant_account_change_trigger', so merchant_send_account_notification()
8678 : had no callers at all and the trigger installed by merchant-0041 was
8679 : still its 'RETURN NULL' stub. */
8680 : {
8681 : struct TALER_MERCHANTDB_AccountDetails account2;
8682 : uint64_t acc_alerts;
8683 :
8684 1 : make_account (&account2);
8685 1 : account2.instance_id = instance.instance.id;
8686 : /* make_account() hands out a fixed payto_uri, which would collide. */
8687 : account2.payto_uri.full_payto
8688 1 : = (char *) "payto://x-taler-bank/bank.demo.taler.net/5";
8689 1 : TEST_RET_ON_FAIL (test_insert_account (&instance,
8690 : &account2,
8691 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8692 1 : TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num"
8693 : " FROM merchant_reports"
8694 : " WHERE report_description"
8695 : " ='automatically triggered account"
8696 : " change alert'",
8697 : &acc_alerts));
8698 1 : TEST_COND_RET_ON_FAIL (1 == acc_alerts,
8699 : "Adding an account did not queue an alert\n");
8700 : }
8701 1 : json_decref (instance.instance.address);
8702 1 : json_decref (instance.instance.jurisdiction);
8703 1 : return 0;
8704 : }
8705 :
8706 :
8707 : /* *********** Templates ********** */
8708 :
8709 : /**
8710 : * A container for data relevant to a template.
8711 : */
8712 : struct TemplateData
8713 : {
8714 : /**
8715 : * The identifier of the template.
8716 : */
8717 : const char *id;
8718 :
8719 : /**
8720 : * The details of the template.
8721 : */
8722 : struct TALER_MERCHANTDB_TemplateDetails template;
8723 : };
8724 :
8725 :
8726 : /**
8727 : * Creates a template for testing with.
8728 : *
8729 : * @param id the id of the template.
8730 : * @param template the template data to fill.
8731 : */
8732 : static void
8733 2 : make_template (const char *id,
8734 : struct TemplateData *template)
8735 : {
8736 2 : template->id = id;
8737 2 : template->template.template_description = "This is a test template";
8738 2 : template->template.otp_id = NULL;
8739 2 : template->template.template_contract = json_array ();
8740 2 : GNUNET_assert (NULL != template->template.template_contract);
8741 2 : }
8742 :
8743 :
8744 : /**
8745 : * Frees memory associated with @e TemplateData.
8746 : *
8747 : * @param template the container to free.
8748 : */
8749 : static void
8750 2 : free_template_data (struct TemplateData *template)
8751 : {
8752 2 : GNUNET_free (template->template.otp_id);
8753 2 : json_decref (template->template.template_contract);
8754 2 : }
8755 :
8756 :
8757 : /**
8758 : * Compare two templates for equality.
8759 : *
8760 : * @param a the first template.
8761 : * @param b the second template.
8762 : * @return 0 on equality, 1 otherwise.
8763 : */
8764 : static int
8765 2 : check_templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *a,
8766 : const struct TALER_MERCHANTDB_TemplateDetails *b)
8767 : {
8768 2 : if ((0 != strcmp (a->template_description,
8769 2 : b->template_description)) ||
8770 2 : ( (NULL == a->otp_id) && (NULL != b->otp_id)) ||
8771 2 : ( (NULL != a->otp_id) && (NULL == b->otp_id)) ||
8772 2 : ( (NULL != a->otp_id) && (0 != strcmp (a->otp_id,
8773 3 : b->otp_id))) ||
8774 2 : (1 != json_equal (a->template_contract,
8775 2 : b->template_contract)))
8776 0 : return 1;
8777 2 : return 0;
8778 : }
8779 :
8780 :
8781 : /**
8782 : * Tests inserting template data into the database.
8783 : *
8784 : * @param instance the instance to insert the template for.
8785 : * @param template the template data to insert.
8786 : * @param expected_result the result we expect the db to return.
8787 : * @return 0 when successful, 1 otherwise.
8788 : */
8789 : static int
8790 4 : test_insert_template (const struct InstanceData *instance,
8791 : const struct TemplateData *template,
8792 : enum GNUNET_DB_QueryStatus expected_result)
8793 : {
8794 4 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
8795 3 : TEST_COND_RET_ON_FAIL (expected_result ==
8796 : TALER_MERCHANTDB_insert_template (pg,
8797 : instance->instance.id,
8798 : template->id,
8799 : 0,
8800 : &template->template),
8801 : "Insert template failed\n");
8802 3 : return 0;
8803 : }
8804 :
8805 :
8806 : /**
8807 : * Tests updating template data in the database.
8808 : *
8809 : * @param instance the instance to update the template for.
8810 : * @param template the template data to update.
8811 : * @param expected_result the result we expect the db to return.
8812 : * @return 0 when successful, 1 otherwise.
8813 : */
8814 : static int
8815 2 : test_update_template (const struct InstanceData *instance,
8816 : const struct TemplateData *template,
8817 : enum GNUNET_DB_QueryStatus expected_result)
8818 : {
8819 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
8820 2 : TEST_COND_RET_ON_FAIL (expected_result ==
8821 : TALER_MERCHANTDB_update_template (pg,
8822 : instance->instance.id,
8823 : template->id,
8824 : &template->template),
8825 : "Update template failed\n");
8826 2 : return 0;
8827 : }
8828 :
8829 :
8830 : /**
8831 : * Tests looking up a template from the db.
8832 : *
8833 : * @param instance the instance to query from.
8834 : * @param template the template to query and compare to.
8835 : * @return 0 when successful, 1 otherwise.
8836 : */
8837 : static int
8838 2 : test_lookup_template (const struct InstanceData *instance,
8839 : const struct TemplateData *template)
8840 : {
8841 2 : const struct TALER_MERCHANTDB_TemplateDetails *to_cmp
8842 : = &template->template;
8843 : struct TALER_MERCHANTDB_TemplateDetails lookup_result;
8844 :
8845 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
8846 2 : if (0 > TALER_MERCHANTDB_get_template (pg,
8847 2 : instance->instance.id,
8848 2 : template->id,
8849 : &lookup_result))
8850 : {
8851 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8852 : "Lookup template failed\n");
8853 0 : TALER_MERCHANTDB_template_details_free (&lookup_result);
8854 0 : return 1;
8855 : }
8856 2 : if (0 != check_templates_equal (&lookup_result,
8857 : to_cmp))
8858 : {
8859 0 : GNUNET_break (0);
8860 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8861 : "Lookup template failed: incorrect template returned\n");
8862 0 : TALER_MERCHANTDB_template_details_free (&lookup_result);
8863 0 : return 1;
8864 : }
8865 2 : TALER_MERCHANTDB_template_details_free (&lookup_result);
8866 2 : return 0;
8867 : }
8868 :
8869 :
8870 : /**
8871 : * Closure for testing template lookup
8872 : */
8873 : struct TestLookupTemplates_Closure
8874 : {
8875 : /**
8876 : * Number of template ids to compare to
8877 : */
8878 : unsigned int templates_to_cmp_length;
8879 :
8880 : /**
8881 : * Pointer to array of template ids
8882 : */
8883 : const struct TemplateData *templates_to_cmp;
8884 :
8885 : /**
8886 : * Pointer to array of number of matches for each template
8887 : */
8888 : unsigned int *results_matching;
8889 :
8890 : /**
8891 : * Total number of results returned
8892 : */
8893 : unsigned int results_length;
8894 : };
8895 :
8896 :
8897 : /**
8898 : * Function called after calling @e test_lookup_templates
8899 : *
8900 : * @param cls a pointer to the lookup closure.
8901 : * @param template_id the identifier of the template found.
8902 : */
8903 : static void
8904 3 : lookup_templates_cb (void *cls,
8905 : const char *template_id,
8906 : const char *template_description)
8907 : {
8908 3 : struct TestLookupTemplates_Closure *cmp = cls;
8909 :
8910 3 : if (NULL == cmp)
8911 0 : return;
8912 3 : cmp->results_length += 1;
8913 8 : for (unsigned int i = 0; cmp->templates_to_cmp_length > i; ++i)
8914 : {
8915 5 : if ( (0 == strcmp (cmp->templates_to_cmp[i].id,
8916 3 : template_id)) &&
8917 3 : (0 == strcmp (cmp->templates_to_cmp[i].template.template_description,
8918 : template_description)) )
8919 3 : cmp->results_matching[i] += 1;
8920 : }
8921 : }
8922 :
8923 :
8924 : /**
8925 : * Tests looking up all templates for an instance.
8926 : *
8927 : * @param instance the instance to query from.
8928 : * @param templates_length the number of templates we are expecting.
8929 : * @param templates the list of templates that we expect to be found.
8930 : * @return 0 when successful, 1 otherwise.
8931 : */
8932 : static int
8933 2 : test_lookup_templates (const struct InstanceData *instance,
8934 : unsigned int templates_length,
8935 : const struct TemplateData *templates)
8936 2 : {
8937 2 : unsigned int results_matching[templates_length];
8938 2 : struct TestLookupTemplates_Closure cls = {
8939 : .templates_to_cmp_length = templates_length,
8940 : .templates_to_cmp = templates,
8941 : .results_matching = results_matching,
8942 : .results_length = 0
8943 : };
8944 :
8945 2 : memset (results_matching,
8946 : 0,
8947 : sizeof (unsigned int) * templates_length);
8948 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
8949 2 : if (0 > TALER_MERCHANTDB_iterate_templates (pg,
8950 2 : instance->instance.id,
8951 : &lookup_templates_cb,
8952 : &cls))
8953 : {
8954 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8955 : "Lookup templates failed\n");
8956 0 : return 1;
8957 : }
8958 2 : if (templates_length != cls.results_length)
8959 : {
8960 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8961 : "Lookup templates failed: incorrect number of results\n");
8962 0 : return 1;
8963 : }
8964 5 : for (unsigned int i = 0; templates_length > i; ++i)
8965 : {
8966 3 : if (1 != cls.results_matching[i])
8967 : {
8968 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8969 : "Lookup templates failed: mismatched data\n");
8970 0 : return 1;
8971 : }
8972 : }
8973 2 : return 0;
8974 : }
8975 :
8976 :
8977 : /**
8978 : * Tests deleting a template.
8979 : *
8980 : * @param instance the instance to delete the template from.
8981 : * @param template the template that should be deleted.
8982 : * @param expected_result the result that we expect the DB to return.
8983 : * @return 0 when successful, 1 otherwise.
8984 : */
8985 : static int
8986 2 : test_delete_template (const struct InstanceData *instance,
8987 : const struct TemplateData *template,
8988 : enum GNUNET_DB_QueryStatus expected_result)
8989 : {
8990 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
8991 2 : TEST_COND_RET_ON_FAIL (expected_result ==
8992 : TALER_MERCHANTDB_delete_template (pg,
8993 : instance->instance.id,
8994 : template->id),
8995 : "Delete template failed\n");
8996 2 : return 0;
8997 : }
8998 :
8999 :
9000 : /**
9001 : * Closure for template tests.
9002 : */
9003 : struct TestTemplates_Closure
9004 : {
9005 : /**
9006 : * The instance to use for this test.
9007 : */
9008 : struct InstanceData instance;
9009 :
9010 : /**
9011 : * The array of templates.
9012 : */
9013 : struct TemplateData templates[2];
9014 : };
9015 :
9016 :
9017 : /**
9018 : * Sets up the data structures used in the template tests.
9019 : *
9020 : * @param cls the closure to fill with test data.
9021 : */
9022 : static void
9023 1 : pre_test_templates (struct TestTemplates_Closure *cls)
9024 : {
9025 : /* Instance */
9026 1 : make_instance ("test_inst_templates",
9027 : &cls->instance);
9028 :
9029 : /* Templates */
9030 1 : make_template ("test_templates_pd_0",
9031 : &cls->templates[0]);
9032 :
9033 1 : make_template ("test_templates_pd_1",
9034 : &cls->templates[1]);
9035 1 : cls->templates[1].template.template_description =
9036 : "This is a another test template";
9037 1 : }
9038 :
9039 :
9040 : /**
9041 : * Handles all teardown after testing.
9042 : *
9043 : * @param cls the closure containing memory to be freed.
9044 : */
9045 : static void
9046 1 : post_test_templates (struct TestTemplates_Closure *cls)
9047 : {
9048 1 : free_instance_data (&cls->instance);
9049 1 : free_template_data (&cls->templates[0]);
9050 1 : free_template_data (&cls->templates[1]);
9051 1 : }
9052 :
9053 :
9054 : /**
9055 : * Runs the tests for templates.
9056 : *
9057 : * @param cls the container of the test data.
9058 : * @return 0 on success, 1 otherwise.
9059 : */
9060 : static int
9061 1 : run_test_templates (struct TestTemplates_Closure *cls)
9062 : {
9063 :
9064 : /* Test that insert without an instance fails */
9065 1 : TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
9066 : &cls->templates[0],
9067 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9068 : /* Insert the instance */
9069 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
9070 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9071 : /* Test inserting a template */
9072 1 : TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
9073 : &cls->templates[0],
9074 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9075 : /* Test that double insert fails */
9076 1 : TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
9077 : &cls->templates[0],
9078 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9079 : /* Test lookup of individual templates */
9080 1 : TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
9081 : &cls->templates[0]));
9082 : /* Make sure it fails correctly for templates that don't exist */
9083 : {
9084 : struct TALER_MERCHANTDB_TemplateDetails tx;
9085 :
9086 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
9087 1 : TALER_MERCHANTDB_get_template (pg,
9088 1 : cls->instance.instance.id,
9089 : "nonexistent_template",
9090 : &tx))
9091 : {
9092 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9093 : "Lookup template failed\n");
9094 0 : return 1;
9095 : }
9096 : }
9097 : /* Test template update */
9098 1 : cls->templates[0].template.template_description =
9099 : "This is a test template that has been updated!";
9100 1 : GNUNET_free (cls->templates[0].template.otp_id);
9101 1 : cls->templates[0].template.otp_id = GNUNET_strdup ("otp_id");
9102 : {
9103 : /* ensure OTP device exists */
9104 1 : struct TALER_MERCHANTDB_OtpDeviceDetails td = {
9105 : .otp_description = "my otp",
9106 : .otp_key = "my key",
9107 : .otp_algorithm = 1,
9108 : .otp_ctr = 42
9109 : };
9110 1 : GNUNET_assert (0 <=
9111 : TALER_MERCHANTDB_insert_otp_device (pg,
9112 : cls->instance.instance.id,
9113 : "otp_id",
9114 : &td));
9115 : }
9116 1 : GNUNET_assert (0 ==
9117 : json_array_append_new (
9118 : cls->templates[0].template.template_contract,
9119 : json_string ("This is a test. 3CH.")));
9120 1 : TEST_RET_ON_FAIL (test_update_template (&cls->instance,
9121 : &cls->templates[0],
9122 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9123 1 : TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
9124 : &cls->templates[0]));
9125 1 : TEST_RET_ON_FAIL (test_update_template (&cls->instance,
9126 : &cls->templates[1],
9127 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9128 : /* Test collective template lookup */
9129 1 : TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
9130 : &cls->templates[1],
9131 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9132 1 : TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
9133 : 2,
9134 : cls->templates));
9135 :
9136 : /* Test template deletion */
9137 1 : TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
9138 : &cls->templates[1],
9139 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9140 : /* Test double deletion fails */
9141 1 : TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
9142 : &cls->templates[1],
9143 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9144 1 : TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
9145 : 1,
9146 : cls->templates));
9147 1 : return 0;
9148 : }
9149 :
9150 :
9151 : /**
9152 : * Takes care of template testing.
9153 : *
9154 : * @return 0 on success, 1 otherwise.
9155 : */
9156 : static int
9157 1 : test_templates (void)
9158 : {
9159 : struct TestTemplates_Closure test_cls;
9160 : int test_result;
9161 :
9162 1 : memset (&test_cls,
9163 : 0,
9164 : sizeof (test_cls));
9165 1 : pre_test_templates (&test_cls);
9166 1 : test_result = run_test_templates (&test_cls);
9167 1 : post_test_templates (&test_cls);
9168 1 : return test_result;
9169 : }
9170 :
9171 :
9172 : /* *********** Webhooks ********** */
9173 :
9174 : /**
9175 : * A container for data relevant to a webhook.
9176 : */
9177 : struct WebhookData
9178 : {
9179 :
9180 : /**
9181 : * The identifier of the webhook.
9182 : */
9183 : const char *id;
9184 :
9185 : /**
9186 : * The details of the webhook.
9187 : */
9188 : struct TALER_MERCHANTDB_WebhookDetails webhook;
9189 : };
9190 :
9191 :
9192 : /**
9193 : * Creates a webhook for testing with.
9194 : *
9195 : * @param id the id of the webhook.
9196 : * @param webhook the webhook data to fill.
9197 : */
9198 : static void
9199 8 : make_webhook (const char *id,
9200 : struct WebhookData *webhook)
9201 : {
9202 8 : webhook->id = id;
9203 8 : webhook->webhook.event_type = "Paid";
9204 8 : webhook->webhook.url = "https://exampletest.com";
9205 8 : webhook->webhook.http_method = "POST";
9206 8 : webhook->webhook.header_template = "Authorization:XYJAORKJEO";
9207 8 : webhook->webhook.body_template = "$Amount";
9208 8 : }
9209 :
9210 :
9211 : /**
9212 : * Compare two webhooks for equality.
9213 : *
9214 : * @param a the first webhook.
9215 : * @param b the second webhook.
9216 : * @return 0 on equality, 1 otherwise.
9217 : */
9218 : static int
9219 2 : check_webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *a,
9220 : const struct TALER_MERCHANTDB_WebhookDetails *b)
9221 : {
9222 2 : if ((0 != strcmp (a->event_type,
9223 2 : b->event_type)) ||
9224 2 : (0 != strcmp (a->url,
9225 2 : b->url)) ||
9226 2 : (0 != strcmp (a->http_method,
9227 2 : b->http_method)) ||
9228 2 : (0 != strcmp (a->header_template,
9229 2 : b->header_template)) ||
9230 2 : (0 != strcmp (a->body_template,
9231 2 : b->body_template)))
9232 0 : return 1;
9233 2 : return 0;
9234 : }
9235 :
9236 :
9237 : /**
9238 : * Tests inserting webhook data into the database.
9239 : *
9240 : * @param instance the instance to insert the webhook for.
9241 : * @param webhook the webhook data to insert.
9242 : * @param expected_result the result we expect the db to return.
9243 : * @return 0 when successful, 1 otherwise.
9244 : */
9245 : static int
9246 10 : test_insert_webhook (const struct InstanceData *instance,
9247 : const struct WebhookData *webhook,
9248 : enum GNUNET_DB_QueryStatus expected_result)
9249 : {
9250 10 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9251 9 : TEST_COND_RET_ON_FAIL (expected_result ==
9252 : TALER_MERCHANTDB_insert_webhook (pg,
9253 : instance->instance.id,
9254 : webhook->id,
9255 : &webhook->webhook),
9256 : "Insert webhook failed\n");
9257 9 : return 0;
9258 : }
9259 :
9260 :
9261 : /**
9262 : * Tests updating webhook data in the database.
9263 : *
9264 : * @param instance the instance to update the webhook for.
9265 : * @param webhook the webhook data to update.
9266 : * @param expected_result the result we expect the db to return.
9267 : * @return 0 when successful, 1 otherwise.
9268 : */
9269 : static int
9270 2 : test_update_webhook (const struct InstanceData *instance,
9271 : const struct WebhookData *webhook,
9272 : enum GNUNET_DB_QueryStatus expected_result)
9273 : {
9274 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9275 2 : TEST_COND_RET_ON_FAIL (expected_result ==
9276 : TALER_MERCHANTDB_update_webhook (pg,
9277 : instance->instance.id,
9278 : webhook->id,
9279 : &webhook->webhook),
9280 : "Update webhook failed\n");
9281 2 : return 0;
9282 : }
9283 :
9284 :
9285 : /**
9286 : * Tests looking up a webhook from the db.
9287 : *
9288 : * @param instance the instance to query from.
9289 : * @param webhook the webhook to query and compare to.
9290 : * @return 0 when successful, 1 otherwise.
9291 : */
9292 : static int
9293 2 : test_lookup_webhook (const struct InstanceData *instance,
9294 : const struct WebhookData *webhook)
9295 : {
9296 2 : const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook;
9297 : struct TALER_MERCHANTDB_WebhookDetails lookup_result;
9298 :
9299 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
9300 2 : if (0 > TALER_MERCHANTDB_get_webhook (pg,
9301 2 : instance->instance.id,
9302 2 : webhook->id,
9303 : &lookup_result))
9304 : {
9305 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9306 : "Lookup webhook failed\n");
9307 0 : TALER_MERCHANTDB_webhook_details_free (&lookup_result);
9308 0 : return 1;
9309 : }
9310 2 : if (0 != check_webhooks_equal (&lookup_result,
9311 : to_cmp))
9312 : {
9313 0 : GNUNET_break (0);
9314 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9315 : "Lookup webhook failed: incorrect webhook returned\n");
9316 0 : TALER_MERCHANTDB_webhook_details_free (&lookup_result);
9317 0 : return 1;
9318 : }
9319 2 : TALER_MERCHANTDB_webhook_details_free (&lookup_result);
9320 2 : return 0;
9321 : }
9322 :
9323 :
9324 : /**
9325 : * Closure for testing webhook lookup
9326 : */
9327 : struct TestLookupWebhooks_Closure
9328 : {
9329 : /**
9330 : * Number of webhook ids to compare to
9331 : */
9332 : unsigned int webhooks_to_cmp_length;
9333 :
9334 : /**
9335 : * Pointer to array of webhook ids
9336 : */
9337 : const struct WebhookData *webhooks_to_cmp;
9338 :
9339 : /**
9340 : * Pointer to array of number of matches for each webhook
9341 : */
9342 : unsigned int *results_matching;
9343 :
9344 : /**
9345 : * Total number of results returned
9346 : */
9347 : unsigned int results_length;
9348 : };
9349 :
9350 :
9351 : /**
9352 : * Function called after calling @e test_lookup_webhooks
9353 : *
9354 : * @param cls a pointer to the lookup closure.
9355 : * @param webhook_id the identifier of the webhook found.
9356 : */
9357 : static void
9358 4 : lookup_webhooks_cb (void *cls,
9359 : const char *webhook_id,
9360 : const char *event_type)
9361 : {
9362 4 : struct TestLookupWebhooks_Closure *cmp = cls;
9363 4 : if (NULL == cmp)
9364 0 : return;
9365 4 : cmp->results_length += 1;
9366 12 : for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
9367 : {
9368 8 : if ((0 == strcmp (cmp->webhooks_to_cmp[i].id,
9369 4 : webhook_id)) &&
9370 4 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
9371 : event_type)) )
9372 4 : cmp->results_matching[i] += 1;
9373 : }
9374 : }
9375 :
9376 :
9377 : /**
9378 : * Tests looking up all webhooks for an instance.
9379 : *
9380 : * @param instance the instance to query from.
9381 : * @param webhooks_length the number of webhooks we are expecting.
9382 : * @param webhooks the list of webhooks that we expect to be found.
9383 : * @return 0 when successful, 1 otherwise.
9384 : */
9385 : static int
9386 2 : test_lookup_webhooks (const struct InstanceData *instance,
9387 : unsigned int webhooks_length,
9388 : const struct WebhookData *webhooks)
9389 2 : {
9390 2 : unsigned int results_matching[webhooks_length];
9391 2 : struct TestLookupWebhooks_Closure cls = {
9392 : .webhooks_to_cmp_length = webhooks_length,
9393 : .webhooks_to_cmp = webhooks,
9394 : .results_matching = results_matching,
9395 : .results_length = 0
9396 : };
9397 2 : memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
9398 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
9399 2 : if (0 > TALER_MERCHANTDB_iterate_webhooks (pg,
9400 2 : instance->instance.id,
9401 : &lookup_webhooks_cb,
9402 : &cls))
9403 : {
9404 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9405 : "Lookup webhooks failed\n");
9406 0 : return 1;
9407 : }
9408 2 : if (webhooks_length != cls.results_length)
9409 : {
9410 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9411 : "Lookup webhooks failed: incorrect number of results\n");
9412 0 : return 1;
9413 : }
9414 6 : for (unsigned int i = 0; webhooks_length > i; ++i)
9415 : {
9416 4 : if (1 != cls.results_matching[i])
9417 : {
9418 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9419 : "Lookup webhooks failed: mismatched data\n");
9420 0 : return 1;
9421 : }
9422 : }
9423 2 : return 0;
9424 : }
9425 :
9426 :
9427 : /**
9428 : * Function called after calling @e test_lookup_webhooks
9429 : *
9430 : * @param cls a pointer to the lookup closure.
9431 : * @param webhook_id the identifier of the webhook found.
9432 : */
9433 : static void
9434 4 : lookup_webhook_by_event_cb (void *cls,
9435 : uint64_t webhook_serial,
9436 : const char *event_type,
9437 : const char *url,
9438 : const char *http_method,
9439 : const char *header_template,
9440 : const char *body_template)
9441 : {
9442 4 : struct TestLookupWebhooks_Closure *cmp = cls;
9443 4 : if (NULL == cmp)
9444 0 : return;
9445 4 : cmp->results_length += 1;
9446 12 : for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
9447 : {
9448 8 : if ((0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
9449 8 : event_type)) &&
9450 8 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.url,
9451 4 : url)) &&
9452 4 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.http_method,
9453 4 : http_method)) &&
9454 4 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.header_template,
9455 4 : header_template)) &&
9456 4 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.body_template,
9457 : body_template)) )
9458 4 : cmp->results_matching[i] += 1;
9459 : }
9460 : }
9461 :
9462 :
9463 : /**
9464 : * Tests looking up webhooks by event for an instance.
9465 : *
9466 : * @param instance the instance to query from.
9467 : * @param webhooks_length the number of webhooks we are expecting.
9468 : * @param webhooks the list of webhooks that we expect to be found.
9469 : * @return 0 when successful, 1 otherwise.
9470 : */
9471 : static int
9472 2 : test_lookup_webhook_by_event (const struct InstanceData *instance,
9473 : unsigned int webhooks_length,
9474 : const struct WebhookData *webhooks)
9475 2 : {
9476 2 : unsigned int results_matching[webhooks_length];
9477 2 : struct TestLookupWebhooks_Closure cls = {
9478 : .webhooks_to_cmp_length = webhooks_length,
9479 : .webhooks_to_cmp = webhooks,
9480 : .results_matching = results_matching,
9481 : .results_length = 0
9482 : };
9483 2 : memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
9484 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
9485 2 : if (0 > TALER_MERCHANTDB_iterate_webhooks_by_event (pg,
9486 2 : instance->instance.id,
9487 2 : webhooks->webhook.event_type,
9488 : &lookup_webhook_by_event_cb,
9489 : &cls))
9490 : {
9491 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9492 : "Lookup webhooks by event failed\n");
9493 0 : return 1;
9494 : }
9495 :
9496 2 : if (webhooks_length != cls.results_length)
9497 : {
9498 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9499 : "Lookup webhooks by event failed: incorrect number of results\n");
9500 0 : return 1;
9501 : }
9502 6 : for (unsigned int i = 0; webhooks_length > i; ++i)
9503 : {
9504 4 : if (1 != cls.results_matching[i])
9505 : {
9506 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9507 : "Lookup webhooks by event failed: mismatched data\n");
9508 0 : return 1;
9509 : }
9510 : }
9511 2 : return 0;
9512 : }
9513 :
9514 :
9515 : /**
9516 : * Tests deleting a webhook.
9517 : *
9518 : * @param instance the instance to delete the webhook from.
9519 : * @param webhook the webhook that should be deleted.
9520 : * @param expected_result the result that we expect the DB to return.
9521 : * @return 0 when successful, 1 otherwise.
9522 : */
9523 : static int
9524 2 : test_delete_webhook (const struct InstanceData *instance,
9525 : const struct WebhookData *webhook,
9526 : enum GNUNET_DB_QueryStatus expected_result)
9527 : {
9528 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9529 2 : TEST_COND_RET_ON_FAIL (expected_result ==
9530 : TALER_MERCHANTDB_delete_webhook (pg,
9531 : instance->instance.id,
9532 : webhook->id),
9533 : "Delete webhook failed\n");
9534 2 : return 0;
9535 : }
9536 :
9537 :
9538 : /**
9539 : * Tests that deleting a product queues an 'inventory_deleted' webhook
9540 : * whose placeholders were resolved from the row that went away.
9541 : *
9542 : * Regression test: handle_inventory_changes() set both substitution
9543 : * flags to FALSE for TG_OP='DELETE', so the pending webhook shipped
9544 : * body_template verbatim -- not even {{webhook_type}} was replaced.
9545 : *
9546 : * Note: this runs on its own instance and *after* the pending webhook
9547 : * tests, which rely on the global merchant_pending_webhooks identity
9548 : * sequence not having been advanced by anybody else.
9549 : *
9550 : * @return 0 when successful, 1 otherwise.
9551 : */
9552 : static int
9553 1 : test_inventory_deleted_webhook (void)
9554 : {
9555 : struct InstanceData instance;
9556 : struct WebhookData webhook;
9557 : struct ProductData product;
9558 1 : char *body = NULL;
9559 1 : int ret = 1;
9560 :
9561 1 : make_instance ("test_inst_inventory_webhooks",
9562 : &instance);
9563 1 : TEST_WITH_FAIL_CLAUSE (test_insert_instance (&instance,
9564 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT),
9565 : free_instance_data (&instance);
9566 : return 1;
9567 : );
9568 1 : make_webhook ("test_webhooks_wb_inventory_deleted",
9569 : &webhook);
9570 1 : webhook.webhook.event_type = "inventory_deleted";
9571 1 : webhook.webhook.url = "https://example.com/inventory-deleted";
9572 1 : webhook.webhook.body_template =
9573 : "{\"type\":\"{{webhook_type}}\""
9574 : ",\"product_id\":\"{{product_id}}\""
9575 : ",\"description\":\"{{description}}\"}";
9576 1 : TEST_WITH_FAIL_CLAUSE (test_insert_webhook (&instance,
9577 : &webhook,
9578 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT),
9579 : free_instance_data (&instance);
9580 : return 1;
9581 : );
9582 1 : make_product ("test_webhooks_deleted_product",
9583 : &product);
9584 1 : TEST_WITH_FAIL_CLAUSE (test_insert_product (&instance,
9585 : &product,
9586 : 0,
9587 : NULL,
9588 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
9589 : false,
9590 : false,
9591 : -1),
9592 : goto cleanup;
9593 : );
9594 1 : TEST_WITH_FAIL_CLAUSE (test_delete_product (&instance,
9595 : &product,
9596 : false,
9597 : false,
9598 : false),
9599 : goto cleanup;
9600 : );
9601 1 : TEST_WITH_FAIL_CLAUSE (
9602 : query_sql_text ("SELECT body AS txt"
9603 : " FROM merchant.merchant_pending_webhooks"
9604 : " WHERE url='https://example.com/inventory-deleted'",
9605 : &body),
9606 : goto cleanup;
9607 : );
9608 1 : if (0 != strcmp ("{\"type\":\"inventory_deleted\""
9609 : ",\"product_id\":\"test_webhooks_deleted_product\""
9610 : ",\"description\":\"This is a test product\"}",
9611 : body))
9612 : {
9613 0 : GNUNET_break (0);
9614 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9615 : "inventory_deleted webhook body not resolved: `%s'\n",
9616 : body);
9617 0 : goto cleanup;
9618 : }
9619 1 : ret = 0;
9620 1 : cleanup:
9621 1 : GNUNET_free (body);
9622 1 : free_product_data (&product);
9623 1 : free_instance_data (&instance);
9624 1 : return ret;
9625 : }
9626 :
9627 :
9628 : /**
9629 : * Closure for webhook tests.
9630 : */
9631 : struct TestWebhooks_Closure
9632 : {
9633 : /**
9634 : * The instance to use for this test.
9635 : */
9636 : struct InstanceData instance;
9637 :
9638 : /**
9639 : * The array of webhooks.
9640 : */
9641 : struct WebhookData webhooks[3];
9642 : };
9643 :
9644 :
9645 : /**
9646 : * Sets up the data structures used in the webhook tests.
9647 : *
9648 : * @param cls the closure to fill with test data.
9649 : */
9650 : static void
9651 1 : pre_test_webhooks (struct TestWebhooks_Closure *cls)
9652 : {
9653 : /* Instance */
9654 1 : make_instance ("test_inst_webhooks",
9655 : &cls->instance);
9656 :
9657 : /* Webhooks */
9658 1 : make_webhook ("test_webhooks_wb_0",
9659 : &cls->webhooks[0]);
9660 :
9661 1 : make_webhook ("test_webhooks_wb_1",
9662 : &cls->webhooks[1]);
9663 1 : cls->webhooks[1].webhook.event_type = "Test paid";
9664 1 : cls->webhooks[1].webhook.url = "https://example.com";
9665 1 : cls->webhooks[1].webhook.http_method = "POST";
9666 1 : cls->webhooks[1].webhook.header_template = "Authorization:1XYJAOR493O";
9667 1 : cls->webhooks[1].webhook.body_template = "$Amount";
9668 :
9669 1 : make_webhook ("test_webhooks_wb_2",
9670 : &cls->webhooks[2]);
9671 1 : cls->webhooks[2].webhook.event_type = "Test paid";
9672 1 : cls->webhooks[2].webhook.url = "https://examplerefund.com";
9673 1 : cls->webhooks[2].webhook.http_method = "POST";
9674 1 : cls->webhooks[2].webhook.header_template = "Authorization:XY6ORK52JEO";
9675 1 : cls->webhooks[2].webhook.body_template = "$Amount";
9676 1 : }
9677 :
9678 :
9679 : /**
9680 : * Handles all teardown after testing.
9681 : *
9682 : * @param cls the closure containing memory to be freed.
9683 : */
9684 : static void
9685 1 : post_test_webhooks (struct TestWebhooks_Closure *cls)
9686 : {
9687 1 : free_instance_data (&cls->instance);
9688 1 : }
9689 :
9690 :
9691 : /**
9692 : * Runs the tests for webhooks.
9693 : *
9694 : * @param cls the container of the test data.
9695 : * @return 0 on success, 1 otherwise.
9696 : */
9697 : static int
9698 1 : run_test_webhooks (struct TestWebhooks_Closure *cls)
9699 : {
9700 :
9701 : /* Test that insert without an instance fails */
9702 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9703 : &cls->webhooks[0],
9704 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9705 : /* Insert the instance */
9706 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
9707 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9708 : /* Test inserting a webhook */
9709 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9710 : &cls->webhooks[0],
9711 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9712 : /* Test that double insert fails */
9713 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9714 : &cls->webhooks[0],
9715 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9716 : /* Test lookup of individual webhooks */
9717 1 : TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
9718 : &cls->webhooks[0]));
9719 : /* Make sure it fails correctly for webhooks that don't exist */
9720 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
9721 1 : TALER_MERCHANTDB_get_webhook (pg,
9722 1 : cls->instance.instance.id,
9723 : "nonexistent_webhook",
9724 : NULL))
9725 : {
9726 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9727 : "Lookup webhook failed\n");
9728 0 : return 1;
9729 : }
9730 : /* Test webhook update */
9731 1 : cls->webhooks[0].webhook.event_type =
9732 : "Test paid";
9733 1 : cls->webhooks[0].webhook.url =
9734 : "example.com";
9735 1 : cls->webhooks[0].webhook.http_method =
9736 : "POST";
9737 1 : cls->webhooks[0].webhook.header_template =
9738 : "Authorization:WEKFOEKEXZ";
9739 1 : cls->webhooks[0].webhook.body_template =
9740 : "$Amount";
9741 1 : TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
9742 : &cls->webhooks[0],
9743 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9744 :
9745 1 : TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
9746 : &cls->webhooks[0]));
9747 1 : TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
9748 : &cls->webhooks[1],
9749 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9750 : /* Test collective webhook lookup */
9751 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9752 : &cls->webhooks[1],
9753 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9754 1 : TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
9755 : 2,
9756 : cls->webhooks));
9757 1 : TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance,
9758 : 2,
9759 : cls->webhooks));
9760 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9761 : &cls->webhooks[2],
9762 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9763 :
9764 : /* Test webhook deletion */
9765 1 : TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance,
9766 : &cls->webhooks[1],
9767 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9768 : /* Test double deletion fails */
9769 1 : TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance,
9770 : &cls->webhooks[1],
9771 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9772 1 : cls->webhooks[1] = cls->webhooks[2];
9773 1 : TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
9774 : 2,
9775 : cls->webhooks));
9776 1 : TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance,
9777 : 2,
9778 : cls->webhooks));
9779 1 : return 0;
9780 : }
9781 :
9782 :
9783 : /**
9784 : * Takes care of webhook testing.
9785 : *
9786 : * @return 0 on success, 1 otherwise.
9787 : */
9788 : static int
9789 1 : test_webhooks (void)
9790 : {
9791 : struct TestWebhooks_Closure test_cls;
9792 : int test_result;
9793 :
9794 1 : pre_test_webhooks (&test_cls);
9795 1 : test_result = run_test_webhooks (&test_cls);
9796 1 : post_test_webhooks (&test_cls);
9797 1 : return test_result;
9798 : }
9799 :
9800 :
9801 : /* *********** Pending Webhooks ********** */
9802 :
9803 : /**
9804 : * A container for data relevant to a pending webhook.
9805 : */
9806 : struct PendingWebhookData
9807 : {
9808 : /**
9809 : * Reference to the configured webhook template.
9810 : */
9811 : uint64_t webhook_serial;
9812 :
9813 : /**
9814 : * The details of the pending webhook.
9815 : */
9816 : struct TALER_MERCHANTDB_PendingWebhookDetails pwebhook;
9817 : };
9818 :
9819 :
9820 : /**
9821 : * Creates a pending webhook for testing with.
9822 : *
9823 : * @param serial reference to the configured webhook template.
9824 : * @param pwebhook the pending webhook data to fill.
9825 : */
9826 : static void
9827 2 : make_pending_webhook (uint64_t webhook_serial,
9828 : struct PendingWebhookData *pwebhook)
9829 : {
9830 2 : pwebhook->webhook_serial = webhook_serial;
9831 2 : pwebhook->pwebhook.next_attempt = GNUNET_TIME_UNIT_ZERO_ABS;
9832 2 : pwebhook->pwebhook.retries = 0;
9833 2 : pwebhook->pwebhook.url = "https://exampletest.com";
9834 2 : pwebhook->pwebhook.http_method = "POST";
9835 2 : pwebhook->pwebhook.header = "Authorization:XYJAORKJEO";
9836 2 : pwebhook->pwebhook.body = "$Amount";
9837 2 : }
9838 :
9839 :
9840 : /**
9841 : * Tests inserting pending webhook data into the database.
9842 : *
9843 : * @param instance the instance to insert the pending webhook for.
9844 : * @param pending webhook the pending webhook data to insert.
9845 : * @param expected_result the result we expect the db to return.
9846 : * @return 0 when successful, 1 otherwise.
9847 : */
9848 : static int
9849 3 : test_insert_pending_webhook (const struct InstanceData *instance,
9850 : struct PendingWebhookData *pwebhook,
9851 : enum GNUNET_DB_QueryStatus expected_result)
9852 : {
9853 :
9854 3 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9855 2 : TEST_COND_RET_ON_FAIL (expected_result ==
9856 : TALER_MERCHANTDB_insert_pending_webhook (pg,
9857 : instance->instance.id,
9858 : pwebhook->
9859 : webhook_serial,
9860 : pwebhook->pwebhook.url,
9861 : pwebhook->pwebhook.
9862 : http_method,
9863 : pwebhook->pwebhook.
9864 : header,
9865 : pwebhook->pwebhook.body
9866 : ),
9867 : "Insert pending webhook failed\n");
9868 2 : return 0;
9869 : }
9870 :
9871 :
9872 : /**
9873 : * Tests updating pending webhook data in the database.
9874 : *
9875 : * @param instance the instance to update the pending webhook for.
9876 : * @param pending webhook the pending webhook data to update.
9877 : * @param expected_result the result we expect the db to return.
9878 : * @return 0 when successful, 1 otherwise.
9879 : */
9880 : static int
9881 2 : test_update_pending_webhook (const struct InstanceData *instance,
9882 : struct PendingWebhookData *pwebhook,
9883 : enum GNUNET_DB_QueryStatus expected_result)
9884 : {
9885 2 : pwebhook->pwebhook.next_attempt = GNUNET_TIME_relative_to_absolute (
9886 : GNUNET_TIME_UNIT_HOURS);
9887 2 : pwebhook->pwebhook.retries++;
9888 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9889 2 : TEST_COND_RET_ON_FAIL (expected_result ==
9890 : TALER_MERCHANTDB_update_pending_webhook (pg,
9891 : pwebhook->
9892 : webhook_serial,
9893 : pwebhook->pwebhook.
9894 : next_attempt),
9895 : "Update pending webhook failed\n");
9896 2 : return 0;
9897 : }
9898 :
9899 :
9900 : /**
9901 : * Container for information for looking up the row number of a deposit.
9902 : */
9903 : struct LookupPendingWebhookSerial_Closure
9904 : {
9905 : /**
9906 : * The pending webhook we're looking for.
9907 : */
9908 : const struct PendingWebhookData *pwebhook;
9909 :
9910 : /**
9911 : * The serial found.
9912 : */
9913 : uint64_t webhook_pending_serial;
9914 : };
9915 :
9916 :
9917 : /**
9918 : * Function called after calling @e test_lookup_all_webhook,
9919 : * test_iterate_pending_webhooks_next and test_lookup_pending_webhook
9920 : *
9921 : * @param cls a pointer to the lookup closure.
9922 : * @param webhook_serial reference to the configured webhook template.
9923 : */
9924 : static void
9925 4 : get_pending_serial_cb (void *cls,
9926 : uint64_t webhook_pending_serial,
9927 : struct GNUNET_TIME_Absolute next_attempt,
9928 : uint32_t retries,
9929 : const char *url,
9930 : const char *http_method,
9931 : const char *header,
9932 : const char *body)
9933 : {
9934 4 : struct LookupPendingWebhookSerial_Closure *lpw = cls;
9935 :
9936 4 : if ((0 == strcmp (lpw->pwebhook->pwebhook.url,
9937 2 : url)) &&
9938 2 : (0 == strcmp (lpw->pwebhook->pwebhook.http_method,
9939 2 : http_method)) &&
9940 2 : (0 == strcmp (lpw->pwebhook->pwebhook.header,
9941 2 : header)) &&
9942 2 : (0 == strcmp (lpw->pwebhook->pwebhook.body,
9943 : body)) )
9944 : {
9945 2 : lpw->webhook_pending_serial = webhook_pending_serial;
9946 : }
9947 : /* else
9948 : {
9949 : fprintf(stdout, "next_attempt: %lu vs %lu\n", lpw->pwebhook->pwebhook.next_attempt.abs_value_us, next_attempt.abs_value_us);
9950 : fprintf(stdout, "retries: %d vs %d\n", lpw->pwebhook->pwebhook.retries, retries);
9951 : fprintf(stdout, "url: %s vs %s\n", lpw->pwebhook->pwebhook.url, url);
9952 : fprintf(stdout, "http_method: %s vs %s\n", lpw->pwebhook->pwebhook.http_method, http_method);
9953 : fprintf(stdout, "header: %s vs %s\n", lpw->pwebhook->pwebhook.header, header);
9954 : fprintf(stdout, "body: %s vs %s\n", lpw->pwebhook->pwebhook.body, body);
9955 : }*/
9956 4 : }
9957 :
9958 :
9959 : /**
9960 : * Convenience function to retrieve the row number of a webhook pending in the database.
9961 : *
9962 : * @param instance the instance to get webhook pending(wp) from.
9963 : * @param webhook pending the wp to lookup the serial for.
9964 : * @return the row number of the deposit.
9965 : */
9966 : static uint64_t
9967 2 : get_pending_serial (const struct InstanceData *instance,
9968 : const struct PendingWebhookData *pwebhook)
9969 : {
9970 2 : struct LookupPendingWebhookSerial_Closure lpw = {
9971 : .pwebhook = pwebhook,
9972 : .webhook_pending_serial = 0
9973 : };
9974 :
9975 2 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
9976 : TALER_MERCHANTDB_set_instance (pg,
9977 : instance->instance.id));
9978 2 : GNUNET_assert (0 <
9979 : TALER_MERCHANTDB_iterate_pending_webhooks_above_serial_id (pg,
9980 : instance->instance.id,
9981 : 0,
9982 : INT_MAX,
9983 : &get_pending_serial_cb,
9984 : &lpw));
9985 2 : GNUNET_assert (0 != lpw.webhook_pending_serial);
9986 :
9987 2 : return lpw.webhook_pending_serial;
9988 : }
9989 :
9990 :
9991 : /**
9992 : * Closure for testing pending webhook lookup
9993 : */
9994 : struct TestLookupPendingWebhooks_Closure
9995 : {
9996 : /**
9997 : * Number of webhook serial to compare to
9998 : */
9999 : unsigned int webhooks_to_cmp_length;
10000 :
10001 : /**
10002 : * Pointer to array of webhook serials
10003 : */
10004 : const struct PendingWebhookData *webhooks_to_cmp;
10005 :
10006 : /**
10007 : * Pointer to array of number of matches for each pending webhook
10008 : */
10009 : unsigned int *results_matching;
10010 :
10011 : /**
10012 : * Total number of results returned
10013 : */
10014 : unsigned int results_length;
10015 : };
10016 :
10017 :
10018 : /**
10019 : * Function called after calling @e test_lookup_all_webhook,
10020 : * test_iterate_pending_webhooks_next and test_lookup_pending_webhook
10021 : *
10022 : * @param cls a pointer to the lookup closure.
10023 : * @param webhook_serial reference to the configured webhook template.
10024 : */
10025 : static void
10026 5 : lookup_pending_webhooks_cb (void *cls,
10027 : uint64_t webhook_pending_serial,
10028 : struct GNUNET_TIME_Absolute next_attempt,
10029 : uint32_t retries,
10030 : const char *url,
10031 : const char *http_method,
10032 : const char *header,
10033 : const char *body)
10034 : {
10035 5 : struct TestLookupPendingWebhooks_Closure *cmp = cls;
10036 :
10037 5 : cmp->results_length++;
10038 14 : for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
10039 : {
10040 9 : if ((0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.url,
10041 5 : url)) &&
10042 5 : (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.http_method,
10043 5 : http_method)) &&
10044 5 : (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.header,
10045 5 : header)) &&
10046 5 : (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.body,
10047 : body)) )
10048 : {
10049 5 : cmp->results_matching[i]++;
10050 : }
10051 : }
10052 5 : }
10053 :
10054 :
10055 : /**
10056 : * Tests looking up the pending webhook for an instance.
10057 : *
10058 : * @param instance the instance to query from.
10059 : * @param pwebhooks_length the number of pending webhook we are expecting.
10060 : * @param pwebhooks the list of pending webhooks that we expect to be found.
10061 : * @return 0 when successful, 1 otherwise.
10062 : */
10063 : static int
10064 1 : test_lookup_pending_webhooks (const struct InstanceData *instance,
10065 : unsigned int pwebhooks_length,
10066 : const struct PendingWebhookData *pwebhooks)
10067 1 : {
10068 1 : unsigned int results_matching[pwebhooks_length];
10069 1 : struct TestLookupPendingWebhooks_Closure cls = {
10070 : .webhooks_to_cmp_length = pwebhooks_length,
10071 : .webhooks_to_cmp = pwebhooks,
10072 : .results_matching = results_matching,
10073 : .results_length = 0
10074 : };
10075 :
10076 1 : memset (results_matching, 0, sizeof (results_matching));
10077 1 : if (0 > TALER_MERCHANTDB_iterate_pending_webhooks (pg,
10078 : 1024, /* limit */
10079 : &lookup_pending_webhooks_cb,
10080 : &cls))
10081 : {
10082 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10083 : "Lookup pending webhook failed\n");
10084 0 : return 1;
10085 : }
10086 1 : if (pwebhooks_length != cls.results_length)
10087 : {
10088 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10089 : "Lookup pending webhook failed: incorrect number of results\n");
10090 0 : return 1;
10091 : }
10092 3 : for (unsigned int i = 0; i < pwebhooks_length; i++)
10093 : {
10094 2 : if (1 != cls.results_matching[i])
10095 : {
10096 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10097 : "Lookup pending webhook failed: mismatched data\n");
10098 0 : return 1;
10099 : }
10100 : }
10101 1 : return 0;
10102 : }
10103 :
10104 :
10105 : /**
10106 : * Tests looking up the future webhook to send for an instance.
10107 : *
10108 : * @param instance the instance to query from.
10109 : * @param pwebhooks_length the number of pending webhook we are expecting.
10110 : * @param pwebhooks the list of pending webhooks that we expect to be found.
10111 : * @return 0 when successful, 1 otherwise.
10112 : */
10113 : static int
10114 1 : test_iterate_pending_webhooks_next (const struct InstanceData *instance,
10115 : unsigned int pwebhooks_length,
10116 : const struct PendingWebhookData *pwebhooks)
10117 1 : {
10118 1 : unsigned int results_matching[pwebhooks_length];
10119 1 : struct TestLookupPendingWebhooks_Closure cls = {
10120 : .webhooks_to_cmp_length = pwebhooks_length,
10121 : .webhooks_to_cmp = pwebhooks,
10122 : .results_matching = results_matching,
10123 : .results_length = 0
10124 : };
10125 :
10126 1 : memset (results_matching, 0, sizeof (results_matching));
10127 1 : if (0 > TALER_MERCHANTDB_iterate_pending_webhooks_next (pg,
10128 : &lookup_pending_webhooks_cb,
10129 : &cls))
10130 : {
10131 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10132 : "Lookup future webhook failed\n");
10133 0 : return 1;
10134 : }
10135 1 : if (pwebhooks_length != cls.results_length)
10136 : {
10137 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10138 : "Lookup future webhook failed: incorrect number of results\n");
10139 0 : return 1;
10140 : }
10141 2 : for (unsigned int i = 0; pwebhooks_length > i; ++i)
10142 : {
10143 1 : if (1 != cls.results_matching[i])
10144 : {
10145 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10146 : "Lookup future webhook failed: mismatched data\n");
10147 0 : return 1;
10148 : }
10149 : }
10150 1 : return 0;
10151 : }
10152 :
10153 :
10154 : /**
10155 : * Tests looking up all the pending webhook for an instance.
10156 : *
10157 : * @param instance the instance to query from.
10158 : * @param pwebhooks_length the number of pending webhook we are expecting.
10159 : * @param pwebhooks the list of pending webhooks that we expect to be found.
10160 : * @return 0 when successful, 1 otherwise.
10161 : */
10162 : static int
10163 2 : test_lookup_all_webhooks (const struct InstanceData *instance,
10164 : unsigned int pwebhooks_length,
10165 : const struct PendingWebhookData *pwebhooks)
10166 2 : {
10167 2 : uint64_t max_results = 2;
10168 2 : uint64_t min_row = 0;
10169 2 : unsigned int results_matching[GNUNET_NZL (pwebhooks_length)];
10170 2 : struct TestLookupPendingWebhooks_Closure cls = {
10171 : .webhooks_to_cmp_length = pwebhooks_length,
10172 : .webhooks_to_cmp = pwebhooks,
10173 : .results_matching = results_matching,
10174 : .results_length = 0
10175 : };
10176 :
10177 2 : memset (results_matching,
10178 : 0,
10179 : sizeof (results_matching));
10180 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10181 2 : if (0 > TALER_MERCHANTDB_iterate_pending_webhooks_above_serial_id (pg,
10182 2 : instance->instance.id,
10183 : min_row,
10184 : max_results,
10185 : &lookup_pending_webhooks_cb,
10186 : &cls))
10187 : {
10188 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10189 : "Lookup all webhooks failed\n");
10190 0 : return 1;
10191 : }
10192 2 : if (pwebhooks_length != cls.results_length)
10193 : {
10194 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10195 : "Lookup all webhooks failed: incorrect number of results\n");
10196 0 : return 1;
10197 : }
10198 4 : for (unsigned int i = 0; pwebhooks_length > i; ++i)
10199 : {
10200 2 : if (1 != cls.results_matching[i])
10201 : {
10202 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10203 : "Lookup all webhooks failed: mismatched data\n");
10204 0 : return 1;
10205 : }
10206 : }
10207 2 : return 0;
10208 : }
10209 :
10210 :
10211 : /**
10212 : * Tests deleting a pending webhook.
10213 : *
10214 : * @param instance the instance to delete the pending webhook from.
10215 : * @param pwebhook the pending webhook that should be deleted.
10216 : * @param expected_result the result that we expect the DB to return.
10217 : * @return 0 when successful, 1 otherwise.
10218 : */
10219 : static int
10220 3 : test_delete_pending_webhook (uint64_t webhooks_pending_serial,
10221 : enum GNUNET_DB_QueryStatus expected_result)
10222 : {
10223 :
10224 3 : TEST_COND_RET_ON_FAIL (expected_result ==
10225 : TALER_MERCHANTDB_delete_pending_webhook (pg,
10226 : webhooks_pending_serial),
10227 : "Delete webhook failed\n");
10228 3 : return 0;
10229 : }
10230 :
10231 :
10232 : /**
10233 : * Closure for pending webhook tests.
10234 : */
10235 : struct TestPendingWebhooks_Closure
10236 : {
10237 : /**
10238 : * The instance to use for this test.
10239 : */
10240 : struct InstanceData instance;
10241 :
10242 : /**
10243 : * Webhooks that the pending webhooks reference via foreign key.
10244 : * Each per-instance schema has its own webhook_serial sequence
10245 : * starting at 1, so we must populate webhooks before inserting
10246 : * pending webhooks.
10247 : */
10248 : struct WebhookData webhooks[4];
10249 :
10250 : /**
10251 : * The array of pending webhooks.
10252 : */
10253 : struct PendingWebhookData pwebhooks[2];
10254 : };
10255 :
10256 :
10257 : /**
10258 : * Sets up the data structures used in the pending webhook tests.
10259 : *
10260 : * @param cls the closure to fill with test data.
10261 : */
10262 : static void
10263 1 : pre_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
10264 : {
10265 : /* Instance */
10266 1 : make_instance ("test_inst_pending_webhooks",
10267 : &cls->instance);
10268 :
10269 : /* Webhooks the pending webhooks reference (serials 1..4) */
10270 1 : make_webhook ("test_pwh_wb_0", &cls->webhooks[0]);
10271 1 : make_webhook ("test_pwh_wb_1", &cls->webhooks[1]);
10272 1 : make_webhook ("test_pwh_wb_2", &cls->webhooks[2]);
10273 1 : make_webhook ("test_pwh_wb_3", &cls->webhooks[3]);
10274 :
10275 : /* Webhooks */
10276 1 : make_pending_webhook (1,
10277 : &cls->pwebhooks[0]);
10278 :
10279 1 : make_pending_webhook (4,
10280 : &cls->pwebhooks[1]);
10281 1 : cls->pwebhooks[1].pwebhook.url = "https://test.com";
10282 1 : cls->pwebhooks[1].pwebhook.http_method = "POST";
10283 1 : cls->pwebhooks[1].pwebhook.header = "Authorization:XYJAO5R06EO";
10284 1 : cls->pwebhooks[1].pwebhook.body = "$Amount";
10285 1 : }
10286 :
10287 :
10288 : /**
10289 : * Handles all teardown after testing.
10290 : *
10291 : * @param cls the closure containing memory to be freed.
10292 : */
10293 : static void
10294 1 : post_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
10295 : {
10296 1 : free_instance_data (&cls->instance);
10297 1 : }
10298 :
10299 :
10300 : /**
10301 : * Runs the tests for pending webhooks.
10302 : *
10303 : * @param cls the container of the test data.
10304 : * @return 0 on success, 1 otherwise.
10305 : */
10306 : static int
10307 1 : run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
10308 : {
10309 : uint64_t webhook_pending_serial0;
10310 : uint64_t webhook_pending_serial1;
10311 :
10312 : /* Test that insert without an instance fails */
10313 1 : TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
10314 : &cls->pwebhooks[0],
10315 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
10316 :
10317 : /* Insert the instance */
10318 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
10319 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10320 :
10321 : /* Insert the referenced webhooks so pending webhook FKs resolve. */
10322 5 : for (unsigned int i = 0; i < 4; i++)
10323 4 : TEST_RET_ON_FAIL (
10324 : test_insert_webhook (&cls->instance,
10325 : &cls->webhooks[i],
10326 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10327 :
10328 : /* Test inserting a pending webhook */
10329 1 : TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
10330 : &cls->pwebhooks[0],
10331 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10332 1 : TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
10333 : &cls->pwebhooks[1],
10334 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10335 : /* Test collective pending webhook lookup */
10336 1 : TEST_RET_ON_FAIL (test_lookup_pending_webhooks (&cls->instance,
10337 : 2,
10338 : cls->pwebhooks));
10339 : /* Test pending webhook update */
10340 1 : TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
10341 : &cls->pwebhooks[0],
10342 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10343 1 : TEST_RET_ON_FAIL (test_iterate_pending_webhooks_next (&cls->instance,
10344 : 1,
10345 : &cls->pwebhooks[1]));
10346 1 : TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
10347 : &cls->pwebhooks[1],
10348 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
10349 : // ???
10350 1 : TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
10351 : 2,
10352 : cls->pwebhooks));
10353 :
10354 1 : webhook_pending_serial0 = get_pending_serial (&cls->instance,
10355 1 : &cls->pwebhooks[0]);
10356 1 : webhook_pending_serial1 = get_pending_serial (&cls->instance,
10357 1 : &cls->pwebhooks[1]);
10358 :
10359 : /* Test webhook deletion */
10360 1 : TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
10361 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10362 : /* Test double deletion fails */
10363 1 : TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
10364 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
10365 1 : TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial0,
10366 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10367 1 : TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
10368 : 0,
10369 : NULL));
10370 1 : return 0;
10371 : }
10372 :
10373 :
10374 : /**
10375 : * Takes care of pending webhook testing.
10376 : *
10377 : * @return 0 on success, 1 otherwise.
10378 : */
10379 : static int
10380 1 : test_pending_webhooks (void)
10381 : {
10382 : struct TestPendingWebhooks_Closure test_cls;
10383 : int test_result;
10384 :
10385 1 : pre_test_pending_webhooks (&test_cls);
10386 1 : test_result = run_test_pending_webhooks (&test_cls);
10387 1 : post_test_pending_webhooks (&test_cls);
10388 1 : return test_result;
10389 : }
10390 :
10391 :
10392 : /* ********** Statistics ********** */
10393 :
10394 :
10395 : /**
10396 : * Closure for the statistics tests.
10397 : */
10398 : struct TestStatistics_Closure
10399 : {
10400 : /**
10401 : * Instance the statistics are collected for.
10402 : */
10403 : struct InstanceData instance;
10404 : };
10405 :
10406 :
10407 : /**
10408 : * Closure for #count_bucket_counter_cb().
10409 : */
10410 : struct CountBuckets_Closure
10411 : {
10412 : /**
10413 : * Number of bucket rows we were called with.
10414 : */
10415 : unsigned int count;
10416 : };
10417 :
10418 :
10419 : /**
10420 : * Runs @a sql directly on the database connection of the test. Used to
10421 : * set up statistics state that has no dedicated MERCHANTDB entry point
10422 : * (the statistics tables are only ever written by triggers).
10423 : *
10424 : * @param sql the SQL statement(s) to execute
10425 : * @return 0 on success, 1 otherwise.
10426 : */
10427 : static int
10428 12 : statistics_exec_sql (const char *sql)
10429 : {
10430 12 : struct GNUNET_PQ_ExecuteStatement es[] = {
10431 12 : GNUNET_PQ_make_execute (sql),
10432 : GNUNET_PQ_EXECUTE_STATEMENT_END
10433 : };
10434 :
10435 12 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10436 : GNUNET_PQ_exec_statements (pg->conn,
10437 : es),
10438 : "Direct SQL execution failed\n");
10439 12 : return 0;
10440 : }
10441 :
10442 :
10443 : /**
10444 : * Counts the bucket rows we are called for.
10445 : *
10446 : * @param cls a `struct CountBuckets_Closure *`
10447 : * @param description description of the statistic
10448 : * @param bucket_start start time of the bucket
10449 : * @param bucket_end end time of the bucket
10450 : * @param bucket_range range of the bucket
10451 : * @param cumulative_counter counter value
10452 : */
10453 : static void
10454 13 : count_bucket_counter_cb (void *cls,
10455 : const char *description,
10456 : struct GNUNET_TIME_Timestamp bucket_start,
10457 : struct GNUNET_TIME_Timestamp bucket_end,
10458 : const char *bucket_range,
10459 : uint64_t cumulative_counter)
10460 : {
10461 13 : struct CountBuckets_Closure *cbc = cls;
10462 :
10463 : (void) description;
10464 : (void) bucket_start;
10465 : (void) bucket_end;
10466 : (void) bucket_range;
10467 : (void) cumulative_counter;
10468 13 : cbc->count++;
10469 13 : }
10470 :
10471 :
10472 : /**
10473 : * Determines how many bucket rows exist for @a slug.
10474 : *
10475 : * @param instance the instance to look at.
10476 : * @param slug the statistic to count the buckets of.
10477 : * @param[out] count set to the number of bucket rows.
10478 : * @return 0 on success, 1 otherwise.
10479 : */
10480 : static int
10481 3 : count_bucket_counters (const struct InstanceData *instance,
10482 : const char *slug,
10483 : unsigned int *count)
10484 : {
10485 3 : struct CountBuckets_Closure cbc = {
10486 : .count = 0
10487 : };
10488 :
10489 3 : TEST_SET_INSTANCE (instance->instance.id,
10490 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10491 3 : TEST_COND_RET_ON_FAIL (0 <=
10492 : TALER_MERCHANTDB_iterate_statistic_bucket_counters (
10493 : pg,
10494 : instance->instance.id,
10495 : slug,
10496 : &count_bucket_counter_cb,
10497 : &cbc),
10498 : "Lookup of bucket statistics failed\n");
10499 3 : *count = cbc.count;
10500 3 : return 0;
10501 : }
10502 :
10503 :
10504 : /**
10505 : * Tests that garbage collection does not throw away bucket statistics
10506 : * that are well within their retention age. Regression test: the
10507 : * retention cut-off in merchant_statistic_bucket_gc() used to be
10508 : * computed with EXTRACT(SECONDS FROM ...), which is the seconds *field*
10509 : * of the interval and thus zero for every range we use, so the cut-off
10510 : * degenerated to "now" and every bucket row was deleted on every run.
10511 : *
10512 : * @param instance the instance to collect statistics for.
10513 : * @return 0 on success, 1 otherwise.
10514 : */
10515 : static int
10516 1 : test_statistics_bucket_gc (const struct InstanceData *instance)
10517 : {
10518 : unsigned int before;
10519 : unsigned int after;
10520 :
10521 1 : TEST_SET_INSTANCE (instance->instance.id,
10522 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10523 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10524 : "CALL merchant_do_bump_number_stat"
10525 : " ('tokens-issued'"
10526 : " ,CURRENT_TIMESTAMP(0)::TIMESTAMP"
10527 : " ,1)"));
10528 1 : TEST_RET_ON_FAIL (count_bucket_counters (instance,
10529 : "tokens-issued",
10530 : &before));
10531 1 : TEST_COND_RET_ON_FAIL (0 < before,
10532 : "Bumping a bucket statistic recorded nothing\n");
10533 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10534 : TALER_MERCHANTDB_gc (pg),
10535 : "Garbage collection failed\n");
10536 1 : TEST_RET_ON_FAIL (count_bucket_counters (instance,
10537 : "tokens-issued",
10538 : &after));
10539 1 : TEST_COND_RET_ON_FAIL (before == after,
10540 : "Garbage collection deleted bucket statistics\n");
10541 1 : return 0;
10542 : }
10543 :
10544 :
10545 : /**
10546 : * Counts the amount buckets we are called for.
10547 : *
10548 : * @param cls a `struct CountBuckets_Closure *`
10549 : * @param description description of the statistic
10550 : * @param bucket_start start time of the bucket
10551 : * @param bucket_end end time of the bucket
10552 : * @param bucket_range range of the bucket
10553 : * @param cumulative_amounts_len length of @a cumulative_amounts
10554 : * @param cumulative_amounts the cumulative amounts
10555 : */
10556 : static void
10557 36 : count_bucket_amount_cb (void *cls,
10558 : const char *description,
10559 : struct GNUNET_TIME_Timestamp bucket_start,
10560 : struct GNUNET_TIME_Timestamp bucket_end,
10561 : const char *bucket_range,
10562 : unsigned int cumulative_amounts_len,
10563 : const struct TALER_Amount cumulative_amounts[static
10564 : cumulative_amounts_len])
10565 36 : {
10566 36 : struct CountBuckets_Closure *cbc = cls;
10567 :
10568 : (void) description;
10569 : (void) bucket_start;
10570 : (void) bucket_end;
10571 : (void) bucket_range;
10572 : (void) cumulative_amounts_len;
10573 : (void) cumulative_amounts;
10574 36 : cbc->count++;
10575 36 : }
10576 :
10577 :
10578 : /**
10579 : * Bumps the amount statistic @a slug and checks that the bump was
10580 : * actually recorded in the bucket statistics. Fails if @a slug is not
10581 : * registered in @a instance, as the bump procedures silently ignore
10582 : * statistics nobody registered.
10583 : *
10584 : * @param instance the instance to collect statistics for.
10585 : * @param slug the statistic to bump.
10586 : * @return 0 on success, 1 otherwise.
10587 : */
10588 : static int
10589 6 : test_statistics_amount_slug_collected (const struct InstanceData *instance,
10590 : const char *slug)
10591 : {
10592 6 : struct CountBuckets_Closure cbc = {
10593 : .count = 0
10594 : };
10595 : char *sql;
10596 :
10597 6 : TEST_SET_INSTANCE (instance->instance.id,
10598 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10599 6 : GNUNET_asprintf (&sql,
10600 : "CALL merchant_do_bump_amount_stat"
10601 : " ('%s'"
10602 : " ,CURRENT_TIMESTAMP(0)::TIMESTAMP"
10603 : " ,ROW(5,0,'EUR')::merchant.taler_amount_currency)",
10604 : slug);
10605 : {
10606 : int ret;
10607 :
10608 6 : ret = statistics_exec_sql (sql);
10609 6 : GNUNET_free (sql);
10610 6 : TEST_RET_ON_FAIL (ret);
10611 : }
10612 6 : TEST_COND_RET_ON_FAIL (0 <=
10613 : TALER_MERCHANTDB_iterate_statistic_bucket_amounts (
10614 : pg,
10615 : instance->instance.id,
10616 : slug,
10617 : &count_bucket_amount_cb,
10618 : &cbc),
10619 : "Lookup of bucket statistics failed\n");
10620 6 : TEST_COND_RET_ON_FAIL (0 < cbc.count,
10621 : "Statistic bumped by a trigger is not registered\n");
10622 6 : return 0;
10623 : }
10624 :
10625 :
10626 : /**
10627 : * Tests that bumping a statistic only uses the bucket ranges of the
10628 : * registration with the matching type. Regression test: the cursor
10629 : * over the ranges did not filter on the type, so a slug registered
10630 : * both as a number and as an amount statistic got buckets for the
10631 : * union of both range sets.
10632 : *
10633 : * @param instance the instance to collect statistics for.
10634 : * @return 0 on success, 1 otherwise.
10635 : */
10636 : static int
10637 1 : test_statistics_dual_type_slug (const struct InstanceData *instance)
10638 : {
10639 : unsigned int count;
10640 :
10641 1 : TEST_SET_INSTANCE (instance->instance.id,
10642 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10643 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10644 : "INSERT INTO merchant_statistic_bucket_meta"
10645 : " (slug"
10646 : " ,description"
10647 : " ,stype"
10648 : " ,ranges"
10649 : " ,ages)"
10650 : " VALUES"
10651 : " ('test-dual-type'"
10652 : " ,'registered as a number statistic'"
10653 : " ,'number'"
10654 : " ,ARRAY['hour'::merchant.statistic_range]"
10655 : " ,ARRAY[72])"
10656 : " ,('test-dual-type'"
10657 : " ,'registered as an amount statistic'"
10658 : " ,'amount'"
10659 : " ,ARRAY['day'::merchant.statistic_range,'week','month']"
10660 : " ,ARRAY[14,12,24])"));
10661 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10662 : "CALL merchant_do_bump_number_stat"
10663 : " ('test-dual-type'"
10664 : " ,CURRENT_TIMESTAMP(0)::TIMESTAMP"
10665 : " ,1)"));
10666 1 : TEST_RET_ON_FAIL (count_bucket_counters (instance,
10667 : "test-dual-type",
10668 : &count));
10669 1 : TEST_COND_RET_ON_FAIL (1 == count,
10670 : "Bump used the ranges of the wrong registration\n");
10671 1 : return 0;
10672 : }
10673 :
10674 :
10675 : /**
10676 : * Determines how many interval events are stored for @a slug.
10677 : *
10678 : * @param instance the instance to look at.
10679 : * @param slug the statistic to count the events of.
10680 : * @param[out] count set to the number of events.
10681 : * @return 0 on success, 1 otherwise.
10682 : */
10683 : static int
10684 1 : statistics_count_counter_events (const struct InstanceData *instance,
10685 : const char *slug,
10686 : uint64_t *count)
10687 : {
10688 1 : struct GNUNET_PQ_QueryParam params[] = {
10689 1 : GNUNET_PQ_query_param_string (slug),
10690 : GNUNET_PQ_query_param_end
10691 : };
10692 1 : struct GNUNET_PQ_ResultSpec rs[] = {
10693 1 : GNUNET_PQ_result_spec_uint64 ("num",
10694 : count),
10695 : GNUNET_PQ_result_spec_end
10696 : };
10697 :
10698 1 : TEST_SET_INSTANCE (instance->instance.id,
10699 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10700 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10701 : GNUNET_PQ_prepare_anon (
10702 : pg->conn,
10703 : "SELECT COUNT(*) AS num"
10704 : " FROM merchant_statistic_counter_event"
10705 : " WHERE imeta_serial_id ="
10706 : " (SELECT imeta_serial_id"
10707 : " FROM merchant_statistic_interval_meta"
10708 : " WHERE slug=$1"
10709 : " AND stype='number')"),
10710 : "Preparing event count failed\n");
10711 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
10712 : GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
10713 : "",
10714 : params,
10715 : rs),
10716 : "Counting interval events failed\n");
10717 1 : return 0;
10718 : }
10719 :
10720 :
10721 : /**
10722 : * Tests that garbage collection survives compacting an interval event
10723 : * that an interval counter still uses as its watermark. Regression
10724 : * test: merchant_statistic_counter_gc() deletes all but the lowest
10725 : * serial of a window, which trips the ON DELETE RESTRICT foreign key
10726 : * of merchant_statistic_interval_counter.event_delimiter as soon as
10727 : * the watermark is not that lowest serial. merchant_do_gc() does not
10728 : * trap that error, so garbage collection was abandoned for all
10729 : * remaining instances.
10730 : *
10731 : * @param instance the instance to collect statistics for.
10732 : * @return 0 on success, 1 otherwise.
10733 : */
10734 : static int
10735 1 : test_statistics_counter_gc_delimiter (const struct InstanceData *instance)
10736 : {
10737 1 : TEST_SET_INSTANCE (instance->instance.id,
10738 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10739 : /* Two events in the same coarsening window, with the watermark of the
10740 : interval counter on the younger (higher serial) one. */
10741 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10742 : "DO $$\n"
10743 : "DECLARE\n"
10744 : " my_time INT8 := ROUND(EXTRACT(epoch FROM"
10745 : " CURRENT_TIMESTAMP(0)::TIMESTAMP))::INT8;\n"
10746 : " my_meta INT8;\n"
10747 : " my_first INT8;\n"
10748 : " my_second INT8;\n"
10749 : "BEGIN\n"
10750 : " SELECT imeta_serial_id\n"
10751 : " INTO my_meta\n"
10752 : " FROM merchant_statistic_interval_meta\n"
10753 : " WHERE slug='orders-created'\n"
10754 : " AND stype='number';\n"
10755 : " INSERT INTO merchant_statistic_counter_event\n"
10756 : " (imeta_serial_id, slot, delta)\n"
10757 : " VALUES (my_meta, my_time - 7000, 1)\n"
10758 : " RETURNING nevent_serial_id INTO my_first;\n"
10759 : " INSERT INTO merchant_statistic_counter_event\n"
10760 : " (imeta_serial_id, slot, delta)\n"
10761 : " VALUES (my_meta, my_time - 6900, 1)\n"
10762 : " RETURNING nevent_serial_id INTO my_second;\n"
10763 : " INSERT INTO merchant_statistic_interval_counter\n"
10764 : " (imeta_serial_id, range, event_delimiter,"
10765 : " cumulative_number)\n"
10766 : " VALUES (my_meta, 7200, my_second, 1);\n"
10767 : "END $$;"));
10768 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10769 : TALER_MERCHANTDB_gc (pg),
10770 : "Garbage collection failed\n");
10771 : {
10772 : uint64_t num_events;
10773 :
10774 1 : TEST_RET_ON_FAIL (statistics_count_counter_events (instance,
10775 : "orders-created",
10776 : &num_events));
10777 1 : TEST_COND_RET_ON_FAIL (1 == num_events,
10778 : "Garbage collection did not compact the events\n");
10779 : }
10780 1 : return 0;
10781 : }
10782 :
10783 :
10784 : /**
10785 : * SQL bumping the 'tokens-used' statistic by one.
10786 : */
10787 : #define BUMP_TOKENS_USED \
10788 : "CALL merchant_do_bump_number_stat" \
10789 : " ('tokens-used'" \
10790 : " ,CURRENT_TIMESTAMP(0)::TIMESTAMP" \
10791 : " ,1)"
10792 :
10793 :
10794 : /**
10795 : * Determines the largest counter value stored for @a slug.
10796 : *
10797 : * @param instance the instance to look at.
10798 : * @param slug the statistic to look at.
10799 : * @param[out] value set to the largest bucket counter.
10800 : * @return 0 on success, 1 otherwise.
10801 : */
10802 : static int
10803 1 : statistics_max_bucket_counter (const struct InstanceData *instance,
10804 : const char *slug,
10805 : uint64_t *value)
10806 : {
10807 1 : struct GNUNET_PQ_QueryParam params[] = {
10808 1 : GNUNET_PQ_query_param_string (slug),
10809 : GNUNET_PQ_query_param_end
10810 : };
10811 1 : struct GNUNET_PQ_ResultSpec rs[] = {
10812 1 : GNUNET_PQ_result_spec_uint64 ("num",
10813 : value),
10814 : GNUNET_PQ_result_spec_end
10815 : };
10816 :
10817 1 : TEST_SET_INSTANCE (instance->instance.id,
10818 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10819 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10820 : GNUNET_PQ_prepare_anon (
10821 : pg->conn,
10822 : "SELECT COALESCE(MAX(cumulative_number),0) AS num"
10823 : " FROM merchant_statistic_bucket_counter"
10824 : " WHERE bmeta_serial_id ="
10825 : " (SELECT bmeta_serial_id"
10826 : " FROM merchant_statistic_bucket_meta"
10827 : " WHERE slug=$1"
10828 : " AND stype='number')"),
10829 : "Preparing counter lookup failed\n");
10830 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
10831 : GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
10832 : "",
10833 : params,
10834 : rs),
10835 : "Looking up bucket counter failed\n");
10836 1 : return 0;
10837 : }
10838 :
10839 :
10840 : /**
10841 : * Bumps the 'tokens-used' statistic from a second process while this
10842 : * process still holds the very first (uncommitted) bump of the same
10843 : * bucket. Regression test: the bucket statistics used to be written
10844 : * as UPDATE-then-INSERT, so the second writer got a unique violation
10845 : * (which GNUnet maps to SUCCESS_NO_RESULTS) and its event was lost;
10846 : * in the pay path the very same pattern surfaces to the wallet as a
10847 : * bogus 409 on a perfectly valid payment.
10848 : *
10849 : * @param instance the instance to collect statistics for.
10850 : * @return 0 on success, 1 otherwise.
10851 : */
10852 : static int
10853 1 : test_statistics_bucket_upsert_race (const struct InstanceData *instance)
10854 : {
10855 : pid_t child;
10856 : int status;
10857 : uint64_t value;
10858 :
10859 1 : TEST_SET_INSTANCE (instance->instance.id,
10860 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10861 : /* Make sure the bucket does not exist yet, only then do both writers
10862 : race to create it. */
10863 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10864 : "DELETE FROM merchant_statistic_bucket_counter"
10865 : " WHERE bmeta_serial_id ="
10866 : " (SELECT bmeta_serial_id"
10867 : " FROM merchant_statistic_bucket_meta"
10868 : " WHERE slug='tokens-used'"
10869 : " AND stype='number')"));
10870 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10871 : TALER_MERCHANTDB_start_read_committed (
10872 : pg,
10873 : "bucket statistics upsert race"),
10874 : "Failed to start transaction\n");
10875 1 : TEST_RET_ON_FAIL (statistics_exec_sql (BUMP_TOKENS_USED));
10876 1 : child = fork ();
10877 1 : if (-1 == child)
10878 : {
10879 0 : TALER_MERCHANTDB_rollback (pg);
10880 0 : GNUNET_break (0);
10881 0 : return 1;
10882 : }
10883 1 : if (0 == child)
10884 : {
10885 : struct TALER_MERCHANTDB_PostgresContext *cpg;
10886 0 : int ret = 1;
10887 :
10888 : /* Second writer: blocks on the uncommitted row of the parent until
10889 : the parent commits below. */
10890 0 : cpg = TALER_MERCHANTDB_connect (test_cfg);
10891 0 : if ( (NULL != cpg) &&
10892 : (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
10893 0 : TALER_MERCHANTDB_set_instance (cpg,
10894 0 : instance->instance.id)) )
10895 : {
10896 0 : struct GNUNET_PQ_ExecuteStatement es[] = {
10897 0 : GNUNET_PQ_make_execute (BUMP_TOKENS_USED),
10898 : GNUNET_PQ_EXECUTE_STATEMENT_END
10899 : };
10900 :
10901 0 : if (GNUNET_OK ==
10902 0 : GNUNET_PQ_exec_statements (cpg->conn,
10903 : es))
10904 0 : ret = 0;
10905 : }
10906 0 : _exit (ret);
10907 : }
10908 1 : sleep (1); /* give the child time to reach the conflicting INSERT */
10909 1 : TEST_COND_RET_ON_FAIL (0 <=
10910 : TALER_MERCHANTDB_commit (pg),
10911 : "Failed to commit transaction\n");
10912 1 : if (0 >= waitpid (child,
10913 : &status,
10914 : 0))
10915 0 : sleep (2); /* cannot reap the child, just give it time to finish */
10916 1 : TEST_RET_ON_FAIL (statistics_max_bucket_counter (instance,
10917 : "tokens-used",
10918 : &value));
10919 1 : TEST_COND_RET_ON_FAIL (2 == value,
10920 : "Concurrent statistics bump was lost\n");
10921 1 : return 0;
10922 : }
10923 :
10924 :
10925 : /**
10926 : * Prepares for the statistics tests.
10927 : *
10928 : * @param[out] cls the closure to initialize.
10929 : */
10930 : static void
10931 1 : pre_test_statistics (struct TestStatistics_Closure *cls)
10932 : {
10933 1 : make_instance ("test_inst_statistics",
10934 : &cls->instance);
10935 1 : }
10936 :
10937 :
10938 : /**
10939 : * Cleans up after the statistics tests.
10940 : *
10941 : * @param[in,out] cls the closure to clean up.
10942 : */
10943 : static void
10944 1 : post_test_statistics (struct TestStatistics_Closure *cls)
10945 : {
10946 1 : free_instance_data (&cls->instance);
10947 1 : }
10948 :
10949 :
10950 : /**
10951 : * Runs the statistics tests.
10952 : *
10953 : * @param cls the closure to use.
10954 : * @return 0 on success, 1 otherwise.
10955 : */
10956 : static int
10957 1 : run_test_statistics (struct TestStatistics_Closure *cls)
10958 : {
10959 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
10960 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10961 1 : TEST_RET_ON_FAIL (test_statistics_bucket_gc (&cls->instance));
10962 : /* Every amount statistic bumped by a trigger in pg_triggers.sql must
10963 : be registered in the per-instance schema. */
10964 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
10965 : &cls->instance,
10966 : "payments-received-after-deposit-fee"));
10967 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
10968 : &cls->instance,
10969 : "total-deposit-fees-paid"));
10970 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
10971 : &cls->instance,
10972 : "total-wire-fees-paid"));
10973 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
10974 : &cls->instance,
10975 : "refunds-granted"));
10976 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
10977 : &cls->instance,
10978 : "deposits-received"));
10979 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
10980 : &cls->instance,
10981 : "deposits-fees-paid"));
10982 1 : TEST_RET_ON_FAIL (test_statistics_counter_gc_delimiter (&cls->instance));
10983 1 : TEST_RET_ON_FAIL (test_statistics_dual_type_slug (&cls->instance));
10984 1 : TEST_RET_ON_FAIL (test_statistics_bucket_upsert_race (&cls->instance));
10985 1 : return 0;
10986 : }
10987 :
10988 :
10989 : /**
10990 : * Takes care of statistics testing.
10991 : *
10992 : * @return 0 on success, 1 otherwise.
10993 : */
10994 : static int
10995 1 : test_statistics (void)
10996 : {
10997 : struct TestStatistics_Closure test_cls;
10998 : int test_result;
10999 :
11000 1 : pre_test_statistics (&test_cls);
11001 1 : test_result = run_test_statistics (&test_cls);
11002 1 : post_test_statistics (&test_cls);
11003 1 : return test_result;
11004 : }
11005 :
11006 :
11007 : /* ********** MFA challenges ********** */
11008 :
11009 :
11010 : /**
11011 : * Tests solving multi-factor authentication (MFA) challenges.
11012 : *
11013 : * Besides the obvious cases, this pins down the *idempotency* of
11014 : * confirming a challenge that has already been confirmed: any TAN is
11015 : * then accepted and the retry counter is left alone. That is
11016 : * intentional (see the comment in do_solve_mfa_challenge.sql): the
11017 : * authorization is carried by the confirmation already stored in the
11018 : * database, which this path does not change, and reaching it already
11019 : * requires knowing the challenge ID together with the matching body
11020 : * hash. The assertions are here so that the contract cannot silently
11021 : * drift.
11022 : *
11023 : * @return 0 on success, 1 otherwise.
11024 : */
11025 : static int
11026 1 : test_mfa_challenges (void)
11027 : {
11028 : struct TALER_MERCHANT_MFA_BodyHash h_body;
11029 : struct TALER_MERCHANT_MFA_BodySalt salt;
11030 : struct TALER_MERCHANT_MFA_BodyHash other_h_body;
11031 : uint64_t challenge_id;
11032 : bool solved;
11033 : uint32_t retry_counter;
11034 :
11035 1 : GNUNET_CRYPTO_random_block (&h_body,
11036 : sizeof (h_body));
11037 1 : GNUNET_CRYPTO_random_block (&other_h_body,
11038 : sizeof (other_h_body));
11039 1 : GNUNET_CRYPTO_random_block (&salt,
11040 : sizeof (salt));
11041 1 : TEST_COND_RET_ON_FAIL (
11042 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
11043 : TALER_MERCHANTDB_insert_mfa_challenge (
11044 : pg,
11045 : "test_inst_mfa",
11046 : TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION,
11047 : &h_body,
11048 : &salt,
11049 : "1234-5678",
11050 : GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS),
11051 : GNUNET_TIME_UNIT_ZERO_ABS,
11052 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
11053 : "test@example.com",
11054 : &challenge_id),
11055 : "Insert MFA challenge failed\n");
11056 : /* A challenge is only addressable with the matching body hash. */
11057 1 : TEST_COND_RET_ON_FAIL (
11058 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
11059 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11060 : challenge_id,
11061 : &other_h_body,
11062 : "1234-5678",
11063 : &solved,
11064 : &retry_counter),
11065 : "Challenge was solvable with a different body hash\n");
11066 1 : TEST_COND_RET_ON_FAIL (! solved,
11067 : "Challenge solved with a different body hash\n");
11068 : /* A wrong TAN is rejected and costs one of the three attempts. */
11069 1 : TEST_COND_RET_ON_FAIL (
11070 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
11071 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11072 : challenge_id,
11073 : &h_body,
11074 : "0000-0000",
11075 : &solved,
11076 : &retry_counter),
11077 : "Solving MFA challenge failed\n");
11078 1 : TEST_COND_RET_ON_FAIL (! solved,
11079 : "A wrong TAN solved the challenge\n");
11080 1 : TEST_COND_RET_ON_FAIL (2 == retry_counter,
11081 : "A wrong TAN did not cost an attempt\n");
11082 : /* The correct TAN confirms the challenge. */
11083 1 : TEST_COND_RET_ON_FAIL (
11084 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
11085 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11086 : challenge_id,
11087 : &h_body,
11088 : "1234-5678",
11089 : &solved,
11090 : &retry_counter),
11091 : "Solving MFA challenge failed\n");
11092 1 : TEST_COND_RET_ON_FAIL (solved,
11093 : "The correct TAN did not solve the challenge\n");
11094 1 : TEST_COND_RET_ON_FAIL (2 == retry_counter,
11095 : "The correct TAN cost an attempt\n");
11096 : /* Re-confirming an already confirmed challenge is idempotent. */
11097 1 : TEST_COND_RET_ON_FAIL (
11098 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
11099 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11100 : challenge_id,
11101 : &h_body,
11102 : "0000-0000",
11103 : &solved,
11104 : &retry_counter),
11105 : "Solving MFA challenge failed\n");
11106 1 : TEST_COND_RET_ON_FAIL (solved,
11107 : "Re-confirming a confirmed challenge is not"
11108 : " idempotent\n");
11109 1 : TEST_COND_RET_ON_FAIL (2 == retry_counter,
11110 : "Re-confirming a confirmed challenge cost an"
11111 : " attempt\n");
11112 : /* ... but still only for the challenge's own body hash. */
11113 1 : TEST_COND_RET_ON_FAIL (
11114 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
11115 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11116 : challenge_id,
11117 : &other_h_body,
11118 : "1234-5678",
11119 : &solved,
11120 : &retry_counter),
11121 : "Confirmed challenge was addressable with a different body hash\n");
11122 1 : TEST_COND_RET_ON_FAIL (! solved,
11123 : "Confirmed challenge solved with a different body"
11124 : " hash\n");
11125 1 : return 0;
11126 : }
11127 :
11128 :
11129 : /**
11130 : * Test that COMMIT on a transaction PostgreSQL has already aborted is not
11131 : * reported as a success -- and that a clean COMMIT still returns exactly
11132 : * GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, which is what every caller that
11133 : * compares the result against 0 relies on.
11134 : *
11135 : * @return 0 on success
11136 : */
11137 : static int
11138 1 : test_commit_on_aborted_transaction (void)
11139 : {
11140 1 : struct GNUNET_PQ_ExecuteStatement mk[] = {
11141 1 : GNUNET_PQ_make_execute ("CREATE TABLE test_commit_rollback (x INT)"),
11142 : GNUNET_PQ_EXECUTE_STATEMENT_END
11143 : };
11144 1 : struct GNUNET_PQ_ExecuteStatement bad[] = {
11145 1 : GNUNET_PQ_make_execute ("SELECT 1/0"),
11146 : GNUNET_PQ_EXECUTE_STATEMENT_END
11147 : };
11148 1 : struct GNUNET_PQ_ExecuteStatement chk[] = {
11149 1 : GNUNET_PQ_make_execute (
11150 : "DO $$ BEGIN"
11151 : " IF EXISTS (SELECT FROM pg_class"
11152 : " WHERE relname='test_commit_rollback')"
11153 : " THEN RAISE EXCEPTION 'table survived a rollback';"
11154 : " END IF; END $$"),
11155 : GNUNET_PQ_EXECUTE_STATEMENT_END
11156 : };
11157 :
11158 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
11159 : TALER_MERCHANTDB_start (pg,
11160 : "test-commit-aborted"),
11161 : "Failed to start transaction\n");
11162 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
11163 : GNUNET_PQ_exec_statements (pg->conn,
11164 : mk),
11165 : "Failed to create the scratch table\n");
11166 : /* provoke an error; from here on the transaction is doomed */
11167 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK !=
11168 : GNUNET_PQ_exec_statements (pg->conn,
11169 : bad),
11170 : "Division by zero unexpectedly succeeded\n");
11171 : /* before the fix this returned GNUNET_DB_STATUS_SUCCESS_NO_RESULTS (0) */
11172 1 : TEST_COND_RET_ON_FAIL (0 >
11173 : TALER_MERCHANTDB_commit (pg),
11174 : "COMMIT of an aborted transaction reported success\n");
11175 : /* ...and the table is indeed gone, so 'success' would have been a lie */
11176 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
11177 : GNUNET_PQ_exec_statements (pg->conn,
11178 : chk),
11179 : "Scratch table survived the rollback\n");
11180 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
11181 : TALER_MERCHANTDB_start (pg,
11182 : "test-commit-clean"),
11183 : "Failed to start transaction\n");
11184 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
11185 : TALER_MERCHANTDB_commit (pg),
11186 : "COMMIT changed its return value on success\n");
11187 1 : return 0;
11188 : }
11189 :
11190 :
11191 : /**
11192 : * Function that runs all tests.
11193 : *
11194 : * @return 0 on success, 1 otherwise.
11195 : */
11196 : static int
11197 1 : run_tests (void)
11198 : {
11199 1 : TEST_RET_ON_FAIL (test_instances ());
11200 1 : TEST_RET_ON_FAIL (test_products ());
11201 1 : TEST_RET_ON_FAIL (test_inventory_locks ());
11202 1 : TEST_RET_ON_FAIL (test_tokens ());
11203 1 : TEST_RET_ON_FAIL (test_orders ());
11204 1 : TEST_RET_ON_FAIL (test_deposits ());
11205 : /* This test relies on the global pending-webhook identity sequence not
11206 : having been advanced by an earlier test. */
11207 1 : TEST_RET_ON_FAIL (test_pending_webhooks ());
11208 1 : TEST_RET_ON_FAIL (test_transfers ());
11209 1 : TEST_RET_ON_FAIL (test_refunds ());
11210 1 : TEST_RET_ON_FAIL (test_lookup_orders_all_filters ());
11211 1 : TEST_RET_ON_FAIL (test_kyc ());
11212 1 : TEST_RET_ON_FAIL (test_templates ());
11213 1 : TEST_RET_ON_FAIL (test_webhooks ());
11214 1 : TEST_RET_ON_FAIL (test_inventory_deleted_webhook ());
11215 1 : TEST_RET_ON_FAIL (test_statistics ());
11216 1 : TEST_RET_ON_FAIL (test_mfa_challenges ());
11217 1 : TEST_RET_ON_FAIL (test_commit_on_aborted_transaction ());
11218 1 : return 0;
11219 : }
11220 :
11221 :
11222 : /**
11223 : * Main function that will be run by the scheduler.
11224 : *
11225 : * @param cls closure with config
11226 : */
11227 : static void
11228 1 : run (void *cls)
11229 : {
11230 1 : struct GNUNET_CONFIGURATION_Handle *cfg = cls;
11231 :
11232 1 : test_cfg = cfg;
11233 1 : if (NULL == (pg = TALER_MERCHANTDB_connect (cfg)))
11234 : {
11235 0 : result = 77;
11236 0 : return;
11237 : }
11238 : /* Drop the tables to cleanup anything that might cause issues */
11239 1 : (void) TALER_MERCHANTDB_drop_tables (pg);
11240 1 : if (GNUNET_OK !=
11241 1 : TALER_MERCHANTDB_create_tables (pg))
11242 : {
11243 0 : result = 77;
11244 0 : return;
11245 : }
11246 : /* Run the preflight */
11247 1 : TALER_MERCHANTDB_preflight (pg);
11248 :
11249 1 : result = run_tests ();
11250 : /* if (0 == result) result = run_test_templates (); */
11251 1 : if (0 == result)
11252 : {
11253 1 : if (GNUNET_OK !=
11254 1 : TALER_MERCHANTDB_drop_tables (pg))
11255 : {
11256 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
11257 : "Dropping tables failed\n");
11258 0 : result = 1;
11259 : }
11260 : }
11261 1 : TALER_MERCHANTDB_disconnect (pg);
11262 1 : pg = NULL;
11263 : }
11264 :
11265 :
11266 : /**
11267 : * Entry point for the tests.
11268 : */
11269 : int
11270 1 : main (int argc,
11271 : char *const argv[])
11272 : {
11273 : struct GNUNET_CONFIGURATION_Handle *cfg;
11274 :
11275 1 : GNUNET_log_setup (argv[0],
11276 : "DEBUG",
11277 : NULL);
11278 1 : cfg = GNUNET_CONFIGURATION_create (TALER_MERCHANT_project_data ());
11279 1 : if (GNUNET_OK !=
11280 1 : GNUNET_CONFIGURATION_parse (cfg,
11281 : "test-merchantdb-postgres.conf"))
11282 : {
11283 0 : GNUNET_break (0);
11284 0 : return 2;
11285 : }
11286 1 : GNUNET_SCHEDULER_run (&run,
11287 : cfg);
11288 1 : GNUNET_CONFIGURATION_destroy (cfg);
11289 1 : return result;
11290 : }
11291 :
11292 :
11293 : /* end of test_merchantdb.c */
|