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 8 : query_sql_num (const char *sql,
247 : uint64_t *num)
248 : {
249 8 : struct GNUNET_PQ_QueryParam params[] = {
250 : GNUNET_PQ_query_param_end
251 : };
252 8 : struct GNUNET_PQ_ResultSpec rs[] = {
253 8 : GNUNET_PQ_result_spec_uint64 ("num",
254 : num),
255 : GNUNET_PQ_result_spec_end
256 : };
257 :
258 8 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
259 : GNUNET_PQ_prepare_anon (pg->conn,
260 : sql),
261 : "Preparing direct query failed\n");
262 8 : 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 8 : 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 3 : 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 3 : struct KycFailureExpected *expected = cls;
8304 :
8305 3 : expected->called++;
8306 3 : if ( (0 != GNUNET_memcmp (&expected->account->h_wire,
8307 3 : h_wire)) ||
8308 3 : (0 != strcmp (expected->account->payto_uri.full_payto,
8309 3 : payto_uri.full_payto)) ||
8310 3 : (0 != strcmp (expected->exchange_url,
8311 3 : exchange_url)) ||
8312 3 : (GNUNET_TIME_timestamp_cmp (expected->timestamp,
8313 : !=,
8314 3 : last_check)) ||
8315 3 : kyc_ok ||
8316 3 : (NULL != access_token) ||
8317 3 : (expected->exchange_http_status != last_http_status) ||
8318 3 : (TALER_EC_NONE != last_ec) ||
8319 3 : in_aml_review ||
8320 : (NULL != jlimits) )
8321 : {
8322 0 : GNUNET_break (0);
8323 0 : expected->failed = true;
8324 : }
8325 3 : }
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 6 : kyc_event_cb (void *cls,
8337 : const void *extra,
8338 : size_t extra_size)
8339 : {
8340 6 : unsigned int *fired = cls;
8341 :
8342 : (void) extra;
8343 : (void) extra_size;
8344 6 : (*fired)++;
8345 6 : }
8346 :
8347 : /**
8348 : * Check the notification encoding used by the SQL refresh request.
8349 : */
8350 : static void
8351 2 : kyc_refresh_event_cb (void *cls,
8352 : const void *extra,
8353 : size_t extra_size)
8354 : {
8355 : uint64_t serial;
8356 :
8357 2 : GNUNET_assert (sizeof (serial) == extra_size);
8358 2 : memcpy (&serial, extra, sizeof (serial));
8359 2 : GNUNET_assert (pg->current_merchant_serial == GNUNET_ntohll (serial));
8360 2 : kyc_event_cb (cls, extra, extra_size);
8361 2 : }
8362 :
8363 :
8364 : /**
8365 : * Test inserting and updating a failed KYC check.
8366 : * Regression test for #10761, which reported that the operation formerly
8367 : * named account_kyc_set_failed had no test coverage.
8368 : *
8369 : * @param instance instance that owns @a account
8370 : * @param account merchant account to use
8371 : * @return 0 on success, 1 otherwise
8372 : */
8373 : static int
8374 1 : test_insert_kyc_failure (
8375 : const struct InstanceData *instance,
8376 : const struct TALER_MERCHANTDB_AccountDetails *account)
8377 : {
8378 : static const char exchange_url[] = "https://exchange2.com/";
8379 1 : struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP account_event = {
8380 1 : .header.size = htons (sizeof (account_event)),
8381 1 : .header.type = htons (
8382 : TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED),
8383 : .merchant_pub = instance->merchant_pub,
8384 : .h_wire = account->h_wire
8385 : };
8386 1 : struct TALER_MERCHANTDB_InstanceKycStatusChangeEventP general_event = {
8387 1 : .header.size = htons (sizeof (general_event)),
8388 1 : .header.type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED),
8389 : .merchant_pub = instance->merchant_pub
8390 : };
8391 1 : struct GNUNET_DB_EventHeaderP refresh_event = {
8392 1 : .size = htons (sizeof (refresh_event)),
8393 1 : .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_UPDATE_FORCED)
8394 : };
8395 1 : struct GNUNET_DB_EventHandler *account_eh = NULL;
8396 1 : struct GNUNET_DB_EventHandler *general_eh = NULL;
8397 1 : struct GNUNET_DB_EventHandler *refresh_eh = NULL;
8398 1 : unsigned int account_events = 0;
8399 1 : unsigned int general_events = 0;
8400 1 : unsigned int refresh_events = 0;
8401 1 : struct KycFailureExpected expected = {
8402 : .account = account,
8403 : .exchange_url = exchange_url,
8404 : .exchange_http_status = MHD_HTTP_BAD_GATEWAY
8405 : };
8406 1 : int ret = 1;
8407 :
8408 1 : TEST_SET_INSTANCE (instance->instance.id,
8409 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
8410 1 : account_eh = TALER_MERCHANTDB_event_listen (
8411 : pg,
8412 : &account_event.header,
8413 1 : GNUNET_TIME_UNIT_FOREVER_REL,
8414 : &kyc_event_cb,
8415 : &account_events);
8416 1 : if (NULL == account_eh)
8417 : {
8418 0 : GNUNET_break (0);
8419 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8420 : "Failed to listen for account KYC events\n");
8421 0 : goto cleanup;
8422 : }
8423 1 : general_eh = TALER_MERCHANTDB_event_listen (
8424 : pg,
8425 : &general_event.header,
8426 1 : GNUNET_TIME_UNIT_FOREVER_REL,
8427 : &kyc_event_cb,
8428 : &general_events);
8429 1 : if (NULL == general_eh)
8430 : {
8431 0 : GNUNET_break (0);
8432 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8433 : "Failed to listen for general KYC events\n");
8434 0 : goto cleanup;
8435 : }
8436 1 : refresh_eh = TALER_MERCHANTDB_event_listen (
8437 : pg,
8438 : &refresh_event,
8439 1 : GNUNET_TIME_UNIT_FOREVER_REL,
8440 : &kyc_refresh_event_cb,
8441 : &refresh_events);
8442 1 : if (NULL == refresh_eh)
8443 : {
8444 0 : GNUNET_break (0);
8445 0 : goto cleanup;
8446 : }
8447 :
8448 1 : expected.timestamp = GNUNET_TIME_timestamp_get ();
8449 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8450 1 : TALER_MERCHANTDB_insert_kyc_failure (
8451 : pg,
8452 1 : instance->instance.id,
8453 : &account->h_wire,
8454 : exchange_url,
8455 : expected.timestamp,
8456 : expected.exchange_http_status,
8457 : false))
8458 : {
8459 0 : GNUNET_break (0);
8460 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8461 : "Inserting KYC failure failed\n");
8462 0 : goto cleanup;
8463 : }
8464 1 : GNUNET_PQ_event_do_poll (pg->conn);
8465 1 : if ( (1 != account_events) ||
8466 1 : (1 != general_events) )
8467 : {
8468 0 : GNUNET_break (0);
8469 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8470 : "Inserting KYC failure generated %u account and %u general"
8471 : " events, expected one each\n",
8472 : account_events,
8473 : general_events);
8474 0 : goto cleanup;
8475 : }
8476 1 : if (1 != TALER_MERCHANTDB_iterate_kyc_statuses (
8477 : pg,
8478 1 : instance->instance.id,
8479 : &account->h_wire,
8480 : exchange_url,
8481 : true,
8482 : &kyc_failure_check,
8483 : &expected))
8484 : {
8485 0 : GNUNET_break (0);
8486 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8487 : "Looking up inserted KYC failure failed\n");
8488 0 : goto cleanup;
8489 : }
8490 1 : if (expected.failed ||
8491 1 : (1 != expected.called))
8492 : {
8493 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8494 : "Inserted KYC failure did not match expectations\n");
8495 0 : goto cleanup;
8496 : }
8497 : /* Reading the status must wake the KYC checker, including an inquiry
8498 : sleeping until its next periodic check. An ACCOUNTS_CHANGED event
8499 : reloads the account list but leaves existing inquiries asleep. */
8500 1 : GNUNET_PQ_event_do_poll (pg->conn);
8501 1 : if (1 != refresh_events)
8502 : {
8503 0 : GNUNET_break (0);
8504 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8505 : "Reading KYC status generated %u forced refresh events,"
8506 : " expected one\n",
8507 : refresh_events);
8508 0 : goto cleanup;
8509 : }
8510 :
8511 1 : expected.timestamp = GNUNET_TIME_timestamp_get ();
8512 1 : expected.exchange_http_status = MHD_HTTP_ACCEPTED;
8513 1 : expected.called = 0;
8514 1 : expected.failed = false;
8515 1 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8516 1 : TALER_MERCHANTDB_insert_kyc_failure (
8517 : pg,
8518 1 : instance->instance.id,
8519 : &account->h_wire,
8520 : exchange_url,
8521 : expected.timestamp,
8522 : expected.exchange_http_status,
8523 : false))
8524 : {
8525 0 : GNUNET_break (0);
8526 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8527 : "Updating KYC failure failed\n");
8528 0 : goto cleanup;
8529 : }
8530 1 : GNUNET_PQ_event_do_poll (pg->conn);
8531 1 : if ( (2 != account_events) ||
8532 1 : (2 != general_events) )
8533 : {
8534 0 : GNUNET_break (0);
8535 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8536 : "Updating KYC failure generated %u account and %u general"
8537 : " events, expected two each\n",
8538 : account_events,
8539 : general_events);
8540 0 : goto cleanup;
8541 : }
8542 1 : if (1 != TALER_MERCHANTDB_iterate_kyc_statuses (
8543 : pg,
8544 1 : instance->instance.id,
8545 : &account->h_wire,
8546 : exchange_url,
8547 : true,
8548 : &kyc_failure_check,
8549 : &expected))
8550 : {
8551 0 : GNUNET_break (0);
8552 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8553 : "Looking up updated KYC failure failed\n");
8554 0 : goto cleanup;
8555 : }
8556 1 : if (expected.failed ||
8557 1 : (1 != expected.called))
8558 : {
8559 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8560 : "Updated KYC failure did not match expectations\n");
8561 0 : goto cleanup;
8562 : }
8563 1 : GNUNET_PQ_event_do_poll (pg->conn);
8564 1 : if (2 != refresh_events)
8565 : {
8566 0 : GNUNET_break (0);
8567 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8568 : "Reading updated KYC status generated %u forced refresh"
8569 : " events, expected two\n",
8570 : refresh_events);
8571 0 : goto cleanup;
8572 : }
8573 : /* A notification-driven reread must neither request another refresh nor
8574 : move the persisted due time. Repeated identical failures are silent. */
8575 : {
8576 : uint64_t before;
8577 : uint64_t after;
8578 :
8579 1 : TEST_RET_ON_FAIL (query_sql_num (
8580 : "SELECT next_kyc_poll AS num FROM merchant_kyc"
8581 : " WHERE exchange_url='https://exchange2.com/'", &before));
8582 1 : expected.called = 0;
8583 1 : GNUNET_assert (1 == TALER_MERCHANTDB_iterate_kyc_statuses (
8584 : pg, instance->instance.id, &account->h_wire,
8585 : exchange_url, false, &kyc_failure_check, &expected));
8586 1 : GNUNET_assert ((! expected.failed) && (1 == expected.called));
8587 1 : TEST_RET_ON_FAIL (query_sql_num (
8588 : "SELECT next_kyc_poll AS num FROM merchant_kyc"
8589 : " WHERE exchange_url='https://exchange2.com/'", &after));
8590 1 : GNUNET_assert (before == after);
8591 1 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
8592 : TALER_MERCHANTDB_insert_kyc_failure (
8593 : pg, instance->instance.id, &account->h_wire,
8594 : exchange_url, GNUNET_TIME_timestamp_get (),
8595 : expected.exchange_http_status, false));
8596 1 : GNUNET_PQ_event_do_poll (pg->conn);
8597 1 : GNUNET_assert (2 == refresh_events);
8598 1 : GNUNET_assert (2 == account_events);
8599 1 : GNUNET_assert (2 == general_events);
8600 : }
8601 1 : ret = 0;
8602 :
8603 1 : cleanup:
8604 1 : if (NULL != refresh_eh)
8605 1 : TALER_MERCHANTDB_event_listen_cancel (refresh_eh);
8606 1 : if (NULL != general_eh)
8607 1 : TALER_MERCHANTDB_event_listen_cancel (general_eh);
8608 1 : if (NULL != account_eh)
8609 1 : TALER_MERCHANTDB_event_listen_cancel (account_eh);
8610 1 : return ret;
8611 : }
8612 :
8613 :
8614 : /**
8615 : * Function that tests the KYC table.
8616 : *
8617 : * @return 0 on success, 1 otherwise.
8618 : */
8619 : static int
8620 1 : test_kyc (void)
8621 : {
8622 : struct InstanceData instance;
8623 : struct TALER_MERCHANTDB_AccountDetails account;
8624 : bool fail;
8625 : struct GNUNET_TIME_Timestamp now;
8626 : uint64_t alerts;
8627 :
8628 1 : make_instance ("test_kyc",
8629 : &instance);
8630 1 : make_account (&account);
8631 1 : account.instance_id = instance.instance.id;
8632 1 : TEST_RET_ON_FAIL (test_insert_instance (&instance,
8633 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8634 1 : TEST_RET_ON_FAIL (test_insert_account (&instance,
8635 : &account,
8636 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8637 : /* Arm the KYC alert path: merchant_send_kyc_notification() returns
8638 : early unless the instance has BOTH an e-mail address and a
8639 : notification language. No C entry point writes
8640 : 'notification_language', hence the direct UPDATE. */
8641 1 : TEST_RET_ON_FAIL (exec_sql ("UPDATE merchant.merchant_instances"
8642 : " SET email='kyc-alerts@example.com'"
8643 : " ,notification_language='en'"
8644 : " WHERE merchant_id='test_kyc'"));
8645 1 : now = GNUNET_TIME_timestamp_get ();
8646 1 : TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8647 : TALER_MERCHANTDB_insert_kyc_status (pg,
8648 : instance.instance.id,
8649 : &account.h_wire,
8650 : "https://exchange.net/",
8651 : now,
8652 : GNUNET_TIME_UNIT_FOREVER_ABS,
8653 : GNUNET_TIME_UNIT_HOURS,
8654 : MHD_HTTP_OK,
8655 : TALER_EC_NONE,
8656 : 42,
8657 : NULL,
8658 : NULL,
8659 : false,
8660 : false));
8661 : /* The AFTER INSERT trigger must have queued exactly one alert.
8662 : Regression test: merchant_send_kyc_notification() used to call
8663 : random_bytea()/uri_escape()/base32_crockford() unqualified -- they
8664 : live in schema 'merchant' only, while the search_path holds just the
8665 : per-instance schema -- and to insert a 'merchant_serial' column that
8666 : merchant-0036 dropped. Either turned this call into a HARD_ERROR,
8667 : which taler-merchant-kyccheck treats as fatal. */
8668 1 : TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num"
8669 : " FROM merchant_reports"
8670 : " WHERE one_shot_hidden",
8671 : &alerts));
8672 1 : TEST_COND_RET_ON_FAIL (1 == alerts,
8673 : "KYC status change did not queue an alert report\n");
8674 1 : TEST_RET_ON_FAIL (test_insert_kyc_failure (&instance,
8675 : &account));
8676 1 : TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8677 : TALER_MERCHANTDB_insert_kyc_status (pg,
8678 : instance.instance.id,
8679 : &account.h_wire,
8680 : "https://exchange.net/",
8681 : now,
8682 : GNUNET_TIME_UNIT_FOREVER_ABS,
8683 : GNUNET_TIME_UNIT_HOURS,
8684 : MHD_HTTP_OK,
8685 : TALER_EC_NONE,
8686 : 42,
8687 : NULL,
8688 : NULL,
8689 : false,
8690 : true));
8691 1 : fail = true;
8692 1 : TEST_RET_ON_FAIL (1 !=
8693 : TALER_MERCHANTDB_iterate_kyc_statuses (pg,
8694 : instance.instance.id,
8695 : &account.h_wire,
8696 : "https://exchange.net/",
8697 : true,
8698 : &kyc_status_ok,
8699 : &fail));
8700 1 : TEST_RET_ON_FAIL (fail);
8701 1 : fail = true;
8702 1 : TEST_RET_ON_FAIL (1 !=
8703 : TALER_MERCHANTDB_iterate_kyc_statuses (pg,
8704 : instance.instance.id,
8705 : NULL,
8706 : "https://exchange2.com/",
8707 : true,
8708 : &kyc_status_fail,
8709 : &fail));
8710 1 : TEST_RET_ON_FAIL (fail);
8711 1 : fail = true;
8712 1 : TEST_RET_ON_FAIL (2 !=
8713 : TALER_MERCHANTDB_iterate_kyc_statuses (pg,
8714 : instance.instance.id,
8715 : NULL,
8716 : NULL,
8717 : true,
8718 : &kyc_status_fail,
8719 : &fail));
8720 1 : TEST_RET_ON_FAIL (fail);
8721 1 : fail = true;
8722 1 : TEST_RET_ON_FAIL (2 !=
8723 : TALER_MERCHANTDB_iterate_kyc_statuses (pg,
8724 : instance.instance.id,
8725 : NULL,
8726 : NULL,
8727 : true,
8728 : &kyc_status_ok,
8729 : &fail));
8730 1 : TEST_RET_ON_FAIL (fail);
8731 : /* A bookkeeping-only re-poll -- kyc_ok, last_rule_gen, aml_review and
8732 : jaccount_limits are unchanged, only the timestamps move -- must NOT
8733 : queue another alert. Regression test: pg_triggers.sql used to
8734 : install a second, unguarded merchant_kyc_update_trigger() on top of
8735 : the guarded one in pg_merchant_kyc_trigger.sql, and being loaded
8736 : last it won, so every next_kyc_poll update alerted the merchant. */
8737 1 : TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num"
8738 : " FROM merchant_reports"
8739 : " WHERE report_description"
8740 : " ='automatically triggered KYC alert'",
8741 : &alerts));
8742 1 : TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
8743 : TALER_MERCHANTDB_insert_kyc_status (pg,
8744 : instance.instance.id,
8745 : &account.h_wire,
8746 : "https://exchange.net/",
8747 : GNUNET_TIME_timestamp_get (),
8748 : GNUNET_TIME_UNIT_FOREVER_ABS,
8749 : GNUNET_TIME_UNIT_MINUTES,
8750 : MHD_HTTP_OK,
8751 : TALER_EC_NONE,
8752 : 42,
8753 : NULL,
8754 : NULL,
8755 : false,
8756 : true));
8757 : {
8758 : uint64_t alerts2;
8759 :
8760 1 : TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num"
8761 : " FROM merchant_reports"
8762 : " WHERE report_description"
8763 : " ='automatically triggered KYC alert'",
8764 : &alerts2));
8765 1 : TEST_COND_RET_ON_FAIL (alerts == alerts2,
8766 : "Bookkeeping-only KYC update queued an alert\n");
8767 : }
8768 : /* Adding an account must queue an account-change alert. Regression
8769 : test: pg_merchant_account_trigger.sql used to (re)define
8770 : 'merchant_kyc_update_trigger' rather than
8771 : 'merchant_account_change_trigger', so merchant_send_account_notification()
8772 : had no callers at all and the trigger installed by merchant-0041 was
8773 : still its 'RETURN NULL' stub. */
8774 : {
8775 : struct TALER_MERCHANTDB_AccountDetails account2;
8776 : uint64_t acc_alerts;
8777 :
8778 1 : make_account (&account2);
8779 1 : account2.instance_id = instance.instance.id;
8780 : /* make_account() hands out a fixed payto_uri, which would collide. */
8781 : account2.payto_uri.full_payto
8782 1 : = (char *) "payto://x-taler-bank/bank.demo.taler.net/5";
8783 1 : TEST_RET_ON_FAIL (test_insert_account (&instance,
8784 : &account2,
8785 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
8786 1 : TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num"
8787 : " FROM merchant_reports"
8788 : " WHERE report_description"
8789 : " ='automatically triggered account"
8790 : " change alert'",
8791 : &acc_alerts));
8792 1 : TEST_COND_RET_ON_FAIL (1 == acc_alerts,
8793 : "Adding an account did not queue an alert\n");
8794 : }
8795 1 : json_decref (instance.instance.address);
8796 1 : json_decref (instance.instance.jurisdiction);
8797 1 : return 0;
8798 : }
8799 :
8800 :
8801 : /* *********** Templates ********** */
8802 :
8803 : /**
8804 : * A container for data relevant to a template.
8805 : */
8806 : struct TemplateData
8807 : {
8808 : /**
8809 : * The identifier of the template.
8810 : */
8811 : const char *id;
8812 :
8813 : /**
8814 : * The details of the template.
8815 : */
8816 : struct TALER_MERCHANTDB_TemplateDetails template;
8817 : };
8818 :
8819 :
8820 : /**
8821 : * Creates a template for testing with.
8822 : *
8823 : * @param id the id of the template.
8824 : * @param template the template data to fill.
8825 : */
8826 : static void
8827 2 : make_template (const char *id,
8828 : struct TemplateData *template)
8829 : {
8830 2 : template->id = id;
8831 2 : template->template.template_description = "This is a test template";
8832 2 : template->template.otp_id = NULL;
8833 2 : template->template.template_contract = json_array ();
8834 2 : GNUNET_assert (NULL != template->template.template_contract);
8835 2 : }
8836 :
8837 :
8838 : /**
8839 : * Frees memory associated with @e TemplateData.
8840 : *
8841 : * @param template the container to free.
8842 : */
8843 : static void
8844 2 : free_template_data (struct TemplateData *template)
8845 : {
8846 2 : GNUNET_free (template->template.otp_id);
8847 2 : json_decref (template->template.template_contract);
8848 2 : }
8849 :
8850 :
8851 : /**
8852 : * Compare two templates for equality.
8853 : *
8854 : * @param a the first template.
8855 : * @param b the second template.
8856 : * @return 0 on equality, 1 otherwise.
8857 : */
8858 : static int
8859 2 : check_templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *a,
8860 : const struct TALER_MERCHANTDB_TemplateDetails *b)
8861 : {
8862 2 : if ((0 != strcmp (a->template_description,
8863 2 : b->template_description)) ||
8864 2 : ( (NULL == a->otp_id) && (NULL != b->otp_id)) ||
8865 2 : ( (NULL != a->otp_id) && (NULL == b->otp_id)) ||
8866 2 : ( (NULL != a->otp_id) && (0 != strcmp (a->otp_id,
8867 3 : b->otp_id))) ||
8868 2 : (1 != json_equal (a->template_contract,
8869 2 : b->template_contract)))
8870 0 : return 1;
8871 2 : return 0;
8872 : }
8873 :
8874 :
8875 : /**
8876 : * Tests inserting template data into the database.
8877 : *
8878 : * @param instance the instance to insert the template for.
8879 : * @param template the template data to insert.
8880 : * @param expected_result the result we expect the db to return.
8881 : * @return 0 when successful, 1 otherwise.
8882 : */
8883 : static int
8884 4 : test_insert_template (const struct InstanceData *instance,
8885 : const struct TemplateData *template,
8886 : enum GNUNET_DB_QueryStatus expected_result)
8887 : {
8888 4 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
8889 3 : TEST_COND_RET_ON_FAIL (expected_result ==
8890 : TALER_MERCHANTDB_insert_template (pg,
8891 : instance->instance.id,
8892 : template->id,
8893 : 0,
8894 : &template->template),
8895 : "Insert template failed\n");
8896 3 : return 0;
8897 : }
8898 :
8899 :
8900 : /**
8901 : * Tests updating template data in the database.
8902 : *
8903 : * @param instance the instance to update the template for.
8904 : * @param template the template data to update.
8905 : * @param expected_result the result we expect the db to return.
8906 : * @return 0 when successful, 1 otherwise.
8907 : */
8908 : static int
8909 2 : test_update_template (const struct InstanceData *instance,
8910 : const struct TemplateData *template,
8911 : enum GNUNET_DB_QueryStatus expected_result)
8912 : {
8913 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
8914 2 : TEST_COND_RET_ON_FAIL (expected_result ==
8915 : TALER_MERCHANTDB_update_template (pg,
8916 : instance->instance.id,
8917 : template->id,
8918 : &template->template),
8919 : "Update template failed\n");
8920 2 : return 0;
8921 : }
8922 :
8923 :
8924 : /**
8925 : * Tests looking up a template from the db.
8926 : *
8927 : * @param instance the instance to query from.
8928 : * @param template the template to query and compare to.
8929 : * @return 0 when successful, 1 otherwise.
8930 : */
8931 : static int
8932 2 : test_lookup_template (const struct InstanceData *instance,
8933 : const struct TemplateData *template)
8934 : {
8935 2 : const struct TALER_MERCHANTDB_TemplateDetails *to_cmp
8936 : = &template->template;
8937 : struct TALER_MERCHANTDB_TemplateDetails lookup_result;
8938 :
8939 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
8940 2 : if (0 > TALER_MERCHANTDB_get_template (pg,
8941 2 : instance->instance.id,
8942 2 : template->id,
8943 : &lookup_result))
8944 : {
8945 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8946 : "Lookup template failed\n");
8947 0 : TALER_MERCHANTDB_template_details_free (&lookup_result);
8948 0 : return 1;
8949 : }
8950 2 : if (0 != check_templates_equal (&lookup_result,
8951 : to_cmp))
8952 : {
8953 0 : GNUNET_break (0);
8954 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
8955 : "Lookup template failed: incorrect template returned\n");
8956 0 : TALER_MERCHANTDB_template_details_free (&lookup_result);
8957 0 : return 1;
8958 : }
8959 2 : TALER_MERCHANTDB_template_details_free (&lookup_result);
8960 2 : return 0;
8961 : }
8962 :
8963 :
8964 : /**
8965 : * Closure for testing template lookup
8966 : */
8967 : struct TestLookupTemplates_Closure
8968 : {
8969 : /**
8970 : * Number of template ids to compare to
8971 : */
8972 : unsigned int templates_to_cmp_length;
8973 :
8974 : /**
8975 : * Pointer to array of template ids
8976 : */
8977 : const struct TemplateData *templates_to_cmp;
8978 :
8979 : /**
8980 : * Pointer to array of number of matches for each template
8981 : */
8982 : unsigned int *results_matching;
8983 :
8984 : /**
8985 : * Total number of results returned
8986 : */
8987 : unsigned int results_length;
8988 : };
8989 :
8990 :
8991 : /**
8992 : * Function called after calling @e test_lookup_templates
8993 : *
8994 : * @param cls a pointer to the lookup closure.
8995 : * @param template_id the identifier of the template found.
8996 : */
8997 : static void
8998 3 : lookup_templates_cb (void *cls,
8999 : const char *template_id,
9000 : const char *template_description)
9001 : {
9002 3 : struct TestLookupTemplates_Closure *cmp = cls;
9003 :
9004 3 : if (NULL == cmp)
9005 0 : return;
9006 3 : cmp->results_length += 1;
9007 8 : for (unsigned int i = 0; cmp->templates_to_cmp_length > i; ++i)
9008 : {
9009 5 : if ( (0 == strcmp (cmp->templates_to_cmp[i].id,
9010 3 : template_id)) &&
9011 3 : (0 == strcmp (cmp->templates_to_cmp[i].template.template_description,
9012 : template_description)) )
9013 3 : cmp->results_matching[i] += 1;
9014 : }
9015 : }
9016 :
9017 :
9018 : /**
9019 : * Tests looking up all templates for an instance.
9020 : *
9021 : * @param instance the instance to query from.
9022 : * @param templates_length the number of templates we are expecting.
9023 : * @param templates the list of templates that we expect to be found.
9024 : * @return 0 when successful, 1 otherwise.
9025 : */
9026 : static int
9027 2 : test_lookup_templates (const struct InstanceData *instance,
9028 : unsigned int templates_length,
9029 : const struct TemplateData *templates)
9030 2 : {
9031 2 : unsigned int results_matching[templates_length];
9032 2 : struct TestLookupTemplates_Closure cls = {
9033 : .templates_to_cmp_length = templates_length,
9034 : .templates_to_cmp = templates,
9035 : .results_matching = results_matching,
9036 : .results_length = 0
9037 : };
9038 :
9039 2 : memset (results_matching,
9040 : 0,
9041 : sizeof (unsigned int) * templates_length);
9042 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
9043 2 : if (0 > TALER_MERCHANTDB_iterate_templates (pg,
9044 2 : instance->instance.id,
9045 : &lookup_templates_cb,
9046 : &cls))
9047 : {
9048 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9049 : "Lookup templates failed\n");
9050 0 : return 1;
9051 : }
9052 2 : if (templates_length != cls.results_length)
9053 : {
9054 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9055 : "Lookup templates failed: incorrect number of results\n");
9056 0 : return 1;
9057 : }
9058 5 : for (unsigned int i = 0; templates_length > i; ++i)
9059 : {
9060 3 : if (1 != cls.results_matching[i])
9061 : {
9062 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9063 : "Lookup templates failed: mismatched data\n");
9064 0 : return 1;
9065 : }
9066 : }
9067 2 : return 0;
9068 : }
9069 :
9070 :
9071 : /**
9072 : * Tests deleting a template.
9073 : *
9074 : * @param instance the instance to delete the template from.
9075 : * @param template the template that should be deleted.
9076 : * @param expected_result the result that we expect the DB to return.
9077 : * @return 0 when successful, 1 otherwise.
9078 : */
9079 : static int
9080 2 : test_delete_template (const struct InstanceData *instance,
9081 : const struct TemplateData *template,
9082 : enum GNUNET_DB_QueryStatus expected_result)
9083 : {
9084 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9085 2 : TEST_COND_RET_ON_FAIL (expected_result ==
9086 : TALER_MERCHANTDB_delete_template (pg,
9087 : instance->instance.id,
9088 : template->id),
9089 : "Delete template failed\n");
9090 2 : return 0;
9091 : }
9092 :
9093 :
9094 : /**
9095 : * Closure for template tests.
9096 : */
9097 : struct TestTemplates_Closure
9098 : {
9099 : /**
9100 : * The instance to use for this test.
9101 : */
9102 : struct InstanceData instance;
9103 :
9104 : /**
9105 : * The array of templates.
9106 : */
9107 : struct TemplateData templates[2];
9108 : };
9109 :
9110 :
9111 : /**
9112 : * Sets up the data structures used in the template tests.
9113 : *
9114 : * @param cls the closure to fill with test data.
9115 : */
9116 : static void
9117 1 : pre_test_templates (struct TestTemplates_Closure *cls)
9118 : {
9119 : /* Instance */
9120 1 : make_instance ("test_inst_templates",
9121 : &cls->instance);
9122 :
9123 : /* Templates */
9124 1 : make_template ("test_templates_pd_0",
9125 : &cls->templates[0]);
9126 :
9127 1 : make_template ("test_templates_pd_1",
9128 : &cls->templates[1]);
9129 1 : cls->templates[1].template.template_description =
9130 : "This is a another test template";
9131 1 : }
9132 :
9133 :
9134 : /**
9135 : * Handles all teardown after testing.
9136 : *
9137 : * @param cls the closure containing memory to be freed.
9138 : */
9139 : static void
9140 1 : post_test_templates (struct TestTemplates_Closure *cls)
9141 : {
9142 1 : free_instance_data (&cls->instance);
9143 1 : free_template_data (&cls->templates[0]);
9144 1 : free_template_data (&cls->templates[1]);
9145 1 : }
9146 :
9147 :
9148 : /**
9149 : * Runs the tests for templates.
9150 : *
9151 : * @param cls the container of the test data.
9152 : * @return 0 on success, 1 otherwise.
9153 : */
9154 : static int
9155 1 : run_test_templates (struct TestTemplates_Closure *cls)
9156 : {
9157 :
9158 : /* Test that insert without an instance fails */
9159 1 : TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
9160 : &cls->templates[0],
9161 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9162 : /* Insert the instance */
9163 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
9164 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9165 : /* Test inserting a template */
9166 1 : TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
9167 : &cls->templates[0],
9168 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9169 : /* Test that double insert fails */
9170 1 : TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
9171 : &cls->templates[0],
9172 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9173 : /* Test lookup of individual templates */
9174 1 : TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
9175 : &cls->templates[0]));
9176 : /* Make sure it fails correctly for templates that don't exist */
9177 : {
9178 : struct TALER_MERCHANTDB_TemplateDetails tx;
9179 :
9180 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
9181 1 : TALER_MERCHANTDB_get_template (pg,
9182 1 : cls->instance.instance.id,
9183 : "nonexistent_template",
9184 : &tx))
9185 : {
9186 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9187 : "Lookup template failed\n");
9188 0 : return 1;
9189 : }
9190 : }
9191 : /* Test template update */
9192 1 : cls->templates[0].template.template_description =
9193 : "This is a test template that has been updated!";
9194 1 : GNUNET_free (cls->templates[0].template.otp_id);
9195 1 : cls->templates[0].template.otp_id = GNUNET_strdup ("otp_id");
9196 : {
9197 : /* ensure OTP device exists */
9198 1 : struct TALER_MERCHANTDB_OtpDeviceDetails td = {
9199 : .otp_description = "my otp",
9200 : .otp_key = "my key",
9201 : .otp_algorithm = 1,
9202 : .otp_ctr = 42
9203 : };
9204 1 : GNUNET_assert (0 <=
9205 : TALER_MERCHANTDB_insert_otp_device (pg,
9206 : cls->instance.instance.id,
9207 : "otp_id",
9208 : &td));
9209 : }
9210 1 : GNUNET_assert (0 ==
9211 : json_array_append_new (
9212 : cls->templates[0].template.template_contract,
9213 : json_string ("This is a test. 3CH.")));
9214 1 : TEST_RET_ON_FAIL (test_update_template (&cls->instance,
9215 : &cls->templates[0],
9216 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9217 1 : TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
9218 : &cls->templates[0]));
9219 1 : TEST_RET_ON_FAIL (test_update_template (&cls->instance,
9220 : &cls->templates[1],
9221 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9222 : /* Test collective template lookup */
9223 1 : TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
9224 : &cls->templates[1],
9225 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9226 1 : TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
9227 : 2,
9228 : cls->templates));
9229 :
9230 : /* Test template deletion */
9231 1 : TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
9232 : &cls->templates[1],
9233 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9234 : /* Test double deletion fails */
9235 1 : TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
9236 : &cls->templates[1],
9237 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9238 1 : TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
9239 : 1,
9240 : cls->templates));
9241 1 : return 0;
9242 : }
9243 :
9244 :
9245 : /**
9246 : * Takes care of template testing.
9247 : *
9248 : * @return 0 on success, 1 otherwise.
9249 : */
9250 : static int
9251 1 : test_templates (void)
9252 : {
9253 : struct TestTemplates_Closure test_cls;
9254 : int test_result;
9255 :
9256 1 : memset (&test_cls,
9257 : 0,
9258 : sizeof (test_cls));
9259 1 : pre_test_templates (&test_cls);
9260 1 : test_result = run_test_templates (&test_cls);
9261 1 : post_test_templates (&test_cls);
9262 1 : return test_result;
9263 : }
9264 :
9265 :
9266 : /* *********** Webhooks ********** */
9267 :
9268 : /**
9269 : * A container for data relevant to a webhook.
9270 : */
9271 : struct WebhookData
9272 : {
9273 :
9274 : /**
9275 : * The identifier of the webhook.
9276 : */
9277 : const char *id;
9278 :
9279 : /**
9280 : * The details of the webhook.
9281 : */
9282 : struct TALER_MERCHANTDB_WebhookDetails webhook;
9283 : };
9284 :
9285 :
9286 : /**
9287 : * Creates a webhook for testing with.
9288 : *
9289 : * @param id the id of the webhook.
9290 : * @param webhook the webhook data to fill.
9291 : */
9292 : static void
9293 8 : make_webhook (const char *id,
9294 : struct WebhookData *webhook)
9295 : {
9296 8 : webhook->id = id;
9297 8 : webhook->webhook.event_type = "Paid";
9298 8 : webhook->webhook.url = "https://exampletest.com";
9299 8 : webhook->webhook.http_method = "POST";
9300 8 : webhook->webhook.header_template = "Authorization:XYJAORKJEO";
9301 8 : webhook->webhook.body_template = "$Amount";
9302 8 : }
9303 :
9304 :
9305 : /**
9306 : * Compare two webhooks for equality.
9307 : *
9308 : * @param a the first webhook.
9309 : * @param b the second webhook.
9310 : * @return 0 on equality, 1 otherwise.
9311 : */
9312 : static int
9313 2 : check_webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *a,
9314 : const struct TALER_MERCHANTDB_WebhookDetails *b)
9315 : {
9316 2 : if ((0 != strcmp (a->event_type,
9317 2 : b->event_type)) ||
9318 2 : (0 != strcmp (a->url,
9319 2 : b->url)) ||
9320 2 : (0 != strcmp (a->http_method,
9321 2 : b->http_method)) ||
9322 2 : (0 != strcmp (a->header_template,
9323 2 : b->header_template)) ||
9324 2 : (0 != strcmp (a->body_template,
9325 2 : b->body_template)))
9326 0 : return 1;
9327 2 : return 0;
9328 : }
9329 :
9330 :
9331 : /**
9332 : * Tests inserting webhook data into the database.
9333 : *
9334 : * @param instance the instance to insert the webhook for.
9335 : * @param webhook the webhook data to insert.
9336 : * @param expected_result the result we expect the db to return.
9337 : * @return 0 when successful, 1 otherwise.
9338 : */
9339 : static int
9340 10 : test_insert_webhook (const struct InstanceData *instance,
9341 : const struct WebhookData *webhook,
9342 : enum GNUNET_DB_QueryStatus expected_result)
9343 : {
9344 10 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9345 9 : TEST_COND_RET_ON_FAIL (expected_result ==
9346 : TALER_MERCHANTDB_insert_webhook (pg,
9347 : instance->instance.id,
9348 : webhook->id,
9349 : &webhook->webhook),
9350 : "Insert webhook failed\n");
9351 9 : return 0;
9352 : }
9353 :
9354 :
9355 : /**
9356 : * Tests updating webhook data in the database.
9357 : *
9358 : * @param instance the instance to update the webhook for.
9359 : * @param webhook the webhook data to update.
9360 : * @param expected_result the result we expect the db to return.
9361 : * @return 0 when successful, 1 otherwise.
9362 : */
9363 : static int
9364 2 : test_update_webhook (const struct InstanceData *instance,
9365 : const struct WebhookData *webhook,
9366 : enum GNUNET_DB_QueryStatus expected_result)
9367 : {
9368 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9369 2 : TEST_COND_RET_ON_FAIL (expected_result ==
9370 : TALER_MERCHANTDB_update_webhook (pg,
9371 : instance->instance.id,
9372 : webhook->id,
9373 : &webhook->webhook),
9374 : "Update webhook failed\n");
9375 2 : return 0;
9376 : }
9377 :
9378 :
9379 : /**
9380 : * Tests looking up a webhook from the db.
9381 : *
9382 : * @param instance the instance to query from.
9383 : * @param webhook the webhook to query and compare to.
9384 : * @return 0 when successful, 1 otherwise.
9385 : */
9386 : static int
9387 2 : test_lookup_webhook (const struct InstanceData *instance,
9388 : const struct WebhookData *webhook)
9389 : {
9390 2 : const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook;
9391 : struct TALER_MERCHANTDB_WebhookDetails lookup_result;
9392 :
9393 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
9394 2 : if (0 > TALER_MERCHANTDB_get_webhook (pg,
9395 2 : instance->instance.id,
9396 2 : webhook->id,
9397 : &lookup_result))
9398 : {
9399 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9400 : "Lookup webhook failed\n");
9401 0 : TALER_MERCHANTDB_webhook_details_free (&lookup_result);
9402 0 : return 1;
9403 : }
9404 2 : if (0 != check_webhooks_equal (&lookup_result,
9405 : to_cmp))
9406 : {
9407 0 : GNUNET_break (0);
9408 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9409 : "Lookup webhook failed: incorrect webhook returned\n");
9410 0 : TALER_MERCHANTDB_webhook_details_free (&lookup_result);
9411 0 : return 1;
9412 : }
9413 2 : TALER_MERCHANTDB_webhook_details_free (&lookup_result);
9414 2 : return 0;
9415 : }
9416 :
9417 :
9418 : /**
9419 : * Closure for testing webhook lookup
9420 : */
9421 : struct TestLookupWebhooks_Closure
9422 : {
9423 : /**
9424 : * Number of webhook ids to compare to
9425 : */
9426 : unsigned int webhooks_to_cmp_length;
9427 :
9428 : /**
9429 : * Pointer to array of webhook ids
9430 : */
9431 : const struct WebhookData *webhooks_to_cmp;
9432 :
9433 : /**
9434 : * Pointer to array of number of matches for each webhook
9435 : */
9436 : unsigned int *results_matching;
9437 :
9438 : /**
9439 : * Total number of results returned
9440 : */
9441 : unsigned int results_length;
9442 : };
9443 :
9444 :
9445 : /**
9446 : * Function called after calling @e test_lookup_webhooks
9447 : *
9448 : * @param cls a pointer to the lookup closure.
9449 : * @param webhook_id the identifier of the webhook found.
9450 : */
9451 : static void
9452 4 : lookup_webhooks_cb (void *cls,
9453 : const char *webhook_id,
9454 : const char *event_type)
9455 : {
9456 4 : struct TestLookupWebhooks_Closure *cmp = cls;
9457 4 : if (NULL == cmp)
9458 0 : return;
9459 4 : cmp->results_length += 1;
9460 12 : for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
9461 : {
9462 8 : if ((0 == strcmp (cmp->webhooks_to_cmp[i].id,
9463 4 : webhook_id)) &&
9464 4 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
9465 : event_type)) )
9466 4 : cmp->results_matching[i] += 1;
9467 : }
9468 : }
9469 :
9470 :
9471 : /**
9472 : * Tests looking up all webhooks for an instance.
9473 : *
9474 : * @param instance the instance to query from.
9475 : * @param webhooks_length the number of webhooks we are expecting.
9476 : * @param webhooks the list of webhooks that we expect to be found.
9477 : * @return 0 when successful, 1 otherwise.
9478 : */
9479 : static int
9480 2 : test_lookup_webhooks (const struct InstanceData *instance,
9481 : unsigned int webhooks_length,
9482 : const struct WebhookData *webhooks)
9483 2 : {
9484 2 : unsigned int results_matching[webhooks_length];
9485 2 : struct TestLookupWebhooks_Closure cls = {
9486 : .webhooks_to_cmp_length = webhooks_length,
9487 : .webhooks_to_cmp = webhooks,
9488 : .results_matching = results_matching,
9489 : .results_length = 0
9490 : };
9491 2 : memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
9492 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
9493 2 : if (0 > TALER_MERCHANTDB_iterate_webhooks (pg,
9494 2 : instance->instance.id,
9495 : &lookup_webhooks_cb,
9496 : &cls))
9497 : {
9498 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9499 : "Lookup webhooks failed\n");
9500 0 : return 1;
9501 : }
9502 2 : if (webhooks_length != cls.results_length)
9503 : {
9504 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9505 : "Lookup webhooks failed: incorrect number of results\n");
9506 0 : return 1;
9507 : }
9508 6 : for (unsigned int i = 0; webhooks_length > i; ++i)
9509 : {
9510 4 : if (1 != cls.results_matching[i])
9511 : {
9512 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9513 : "Lookup webhooks failed: mismatched data\n");
9514 0 : return 1;
9515 : }
9516 : }
9517 2 : return 0;
9518 : }
9519 :
9520 :
9521 : /**
9522 : * Function called after calling @e test_lookup_webhooks
9523 : *
9524 : * @param cls a pointer to the lookup closure.
9525 : * @param webhook_id the identifier of the webhook found.
9526 : */
9527 : static void
9528 4 : lookup_webhook_by_event_cb (void *cls,
9529 : uint64_t webhook_serial,
9530 : const char *event_type,
9531 : const char *url,
9532 : const char *http_method,
9533 : const char *header_template,
9534 : const char *body_template)
9535 : {
9536 4 : struct TestLookupWebhooks_Closure *cmp = cls;
9537 4 : if (NULL == cmp)
9538 0 : return;
9539 4 : cmp->results_length += 1;
9540 12 : for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
9541 : {
9542 8 : if ((0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
9543 8 : event_type)) &&
9544 8 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.url,
9545 4 : url)) &&
9546 4 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.http_method,
9547 4 : http_method)) &&
9548 4 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.header_template,
9549 4 : header_template)) &&
9550 4 : (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.body_template,
9551 : body_template)) )
9552 4 : cmp->results_matching[i] += 1;
9553 : }
9554 : }
9555 :
9556 :
9557 : /**
9558 : * Tests looking up webhooks by event for an instance.
9559 : *
9560 : * @param instance the instance to query from.
9561 : * @param webhooks_length the number of webhooks we are expecting.
9562 : * @param webhooks the list of webhooks that we expect to be found.
9563 : * @return 0 when successful, 1 otherwise.
9564 : */
9565 : static int
9566 2 : test_lookup_webhook_by_event (const struct InstanceData *instance,
9567 : unsigned int webhooks_length,
9568 : const struct WebhookData *webhooks)
9569 2 : {
9570 2 : unsigned int results_matching[webhooks_length];
9571 2 : struct TestLookupWebhooks_Closure cls = {
9572 : .webhooks_to_cmp_length = webhooks_length,
9573 : .webhooks_to_cmp = webhooks,
9574 : .results_matching = results_matching,
9575 : .results_length = 0
9576 : };
9577 2 : memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
9578 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
9579 2 : if (0 > TALER_MERCHANTDB_iterate_webhooks_by_event (pg,
9580 2 : instance->instance.id,
9581 2 : webhooks->webhook.event_type,
9582 : &lookup_webhook_by_event_cb,
9583 : &cls))
9584 : {
9585 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9586 : "Lookup webhooks by event failed\n");
9587 0 : return 1;
9588 : }
9589 :
9590 2 : if (webhooks_length != cls.results_length)
9591 : {
9592 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9593 : "Lookup webhooks by event failed: incorrect number of results\n");
9594 0 : return 1;
9595 : }
9596 6 : for (unsigned int i = 0; webhooks_length > i; ++i)
9597 : {
9598 4 : if (1 != cls.results_matching[i])
9599 : {
9600 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9601 : "Lookup webhooks by event failed: mismatched data\n");
9602 0 : return 1;
9603 : }
9604 : }
9605 2 : return 0;
9606 : }
9607 :
9608 :
9609 : /**
9610 : * Tests deleting a webhook.
9611 : *
9612 : * @param instance the instance to delete the webhook from.
9613 : * @param webhook the webhook that should be deleted.
9614 : * @param expected_result the result that we expect the DB to return.
9615 : * @return 0 when successful, 1 otherwise.
9616 : */
9617 : static int
9618 2 : test_delete_webhook (const struct InstanceData *instance,
9619 : const struct WebhookData *webhook,
9620 : enum GNUNET_DB_QueryStatus expected_result)
9621 : {
9622 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9623 2 : TEST_COND_RET_ON_FAIL (expected_result ==
9624 : TALER_MERCHANTDB_delete_webhook (pg,
9625 : instance->instance.id,
9626 : webhook->id),
9627 : "Delete webhook failed\n");
9628 2 : return 0;
9629 : }
9630 :
9631 :
9632 : /**
9633 : * Tests that deleting a product queues an 'inventory_deleted' webhook
9634 : * whose placeholders were resolved from the row that went away.
9635 : *
9636 : * Regression test: handle_inventory_changes() set both substitution
9637 : * flags to FALSE for TG_OP='DELETE', so the pending webhook shipped
9638 : * body_template verbatim -- not even {{webhook_type}} was replaced.
9639 : *
9640 : * Note: this runs on its own instance and *after* the pending webhook
9641 : * tests, which rely on the global merchant_pending_webhooks identity
9642 : * sequence not having been advanced by anybody else.
9643 : *
9644 : * @return 0 when successful, 1 otherwise.
9645 : */
9646 : static int
9647 1 : test_inventory_deleted_webhook (void)
9648 : {
9649 : struct InstanceData instance;
9650 : struct WebhookData webhook;
9651 : struct ProductData product;
9652 1 : char *body = NULL;
9653 1 : int ret = 1;
9654 :
9655 1 : make_instance ("test_inst_inventory_webhooks",
9656 : &instance);
9657 1 : TEST_WITH_FAIL_CLAUSE (test_insert_instance (&instance,
9658 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT),
9659 : free_instance_data (&instance);
9660 : return 1;
9661 : );
9662 1 : make_webhook ("test_webhooks_wb_inventory_deleted",
9663 : &webhook);
9664 1 : webhook.webhook.event_type = "inventory_deleted";
9665 1 : webhook.webhook.url = "https://example.com/inventory-deleted";
9666 1 : webhook.webhook.body_template =
9667 : "{\"type\":\"{{webhook_type}}\""
9668 : ",\"product_id\":\"{{product_id}}\""
9669 : ",\"description\":\"{{description}}\"}";
9670 1 : TEST_WITH_FAIL_CLAUSE (test_insert_webhook (&instance,
9671 : &webhook,
9672 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT),
9673 : free_instance_data (&instance);
9674 : return 1;
9675 : );
9676 1 : make_product ("test_webhooks_deleted_product",
9677 : &product);
9678 1 : TEST_WITH_FAIL_CLAUSE (test_insert_product (&instance,
9679 : &product,
9680 : 0,
9681 : NULL,
9682 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
9683 : false,
9684 : false,
9685 : -1),
9686 : goto cleanup;
9687 : );
9688 1 : TEST_WITH_FAIL_CLAUSE (test_delete_product (&instance,
9689 : &product,
9690 : false,
9691 : false,
9692 : false),
9693 : goto cleanup;
9694 : );
9695 1 : TEST_WITH_FAIL_CLAUSE (
9696 : query_sql_text ("SELECT body AS txt"
9697 : " FROM merchant.merchant_pending_webhooks"
9698 : " WHERE url='https://example.com/inventory-deleted'",
9699 : &body),
9700 : goto cleanup;
9701 : );
9702 1 : if (0 != strcmp ("{\"type\":\"inventory_deleted\""
9703 : ",\"product_id\":\"test_webhooks_deleted_product\""
9704 : ",\"description\":\"This is a test product\"}",
9705 : body))
9706 : {
9707 0 : GNUNET_break (0);
9708 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9709 : "inventory_deleted webhook body not resolved: `%s'\n",
9710 : body);
9711 0 : goto cleanup;
9712 : }
9713 1 : ret = 0;
9714 1 : cleanup:
9715 1 : GNUNET_free (body);
9716 1 : free_product_data (&product);
9717 1 : free_instance_data (&instance);
9718 1 : return ret;
9719 : }
9720 :
9721 :
9722 : /**
9723 : * Closure for webhook tests.
9724 : */
9725 : struct TestWebhooks_Closure
9726 : {
9727 : /**
9728 : * The instance to use for this test.
9729 : */
9730 : struct InstanceData instance;
9731 :
9732 : /**
9733 : * The array of webhooks.
9734 : */
9735 : struct WebhookData webhooks[3];
9736 : };
9737 :
9738 :
9739 : /**
9740 : * Sets up the data structures used in the webhook tests.
9741 : *
9742 : * @param cls the closure to fill with test data.
9743 : */
9744 : static void
9745 1 : pre_test_webhooks (struct TestWebhooks_Closure *cls)
9746 : {
9747 : /* Instance */
9748 1 : make_instance ("test_inst_webhooks",
9749 : &cls->instance);
9750 :
9751 : /* Webhooks */
9752 1 : make_webhook ("test_webhooks_wb_0",
9753 : &cls->webhooks[0]);
9754 :
9755 1 : make_webhook ("test_webhooks_wb_1",
9756 : &cls->webhooks[1]);
9757 1 : cls->webhooks[1].webhook.event_type = "Test paid";
9758 1 : cls->webhooks[1].webhook.url = "https://example.com";
9759 1 : cls->webhooks[1].webhook.http_method = "POST";
9760 1 : cls->webhooks[1].webhook.header_template = "Authorization:1XYJAOR493O";
9761 1 : cls->webhooks[1].webhook.body_template = "$Amount";
9762 :
9763 1 : make_webhook ("test_webhooks_wb_2",
9764 : &cls->webhooks[2]);
9765 1 : cls->webhooks[2].webhook.event_type = "Test paid";
9766 1 : cls->webhooks[2].webhook.url = "https://examplerefund.com";
9767 1 : cls->webhooks[2].webhook.http_method = "POST";
9768 1 : cls->webhooks[2].webhook.header_template = "Authorization:XY6ORK52JEO";
9769 1 : cls->webhooks[2].webhook.body_template = "$Amount";
9770 1 : }
9771 :
9772 :
9773 : /**
9774 : * Handles all teardown after testing.
9775 : *
9776 : * @param cls the closure containing memory to be freed.
9777 : */
9778 : static void
9779 1 : post_test_webhooks (struct TestWebhooks_Closure *cls)
9780 : {
9781 1 : free_instance_data (&cls->instance);
9782 1 : }
9783 :
9784 :
9785 : /**
9786 : * Runs the tests for webhooks.
9787 : *
9788 : * @param cls the container of the test data.
9789 : * @return 0 on success, 1 otherwise.
9790 : */
9791 : static int
9792 1 : run_test_webhooks (struct TestWebhooks_Closure *cls)
9793 : {
9794 :
9795 : /* Test that insert without an instance fails */
9796 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9797 : &cls->webhooks[0],
9798 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9799 : /* Insert the instance */
9800 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
9801 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9802 : /* Test inserting a webhook */
9803 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9804 : &cls->webhooks[0],
9805 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9806 : /* Test that double insert fails */
9807 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9808 : &cls->webhooks[0],
9809 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9810 : /* Test lookup of individual webhooks */
9811 1 : TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
9812 : &cls->webhooks[0]));
9813 : /* Make sure it fails correctly for webhooks that don't exist */
9814 1 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
9815 1 : TALER_MERCHANTDB_get_webhook (pg,
9816 1 : cls->instance.instance.id,
9817 : "nonexistent_webhook",
9818 : NULL))
9819 : {
9820 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
9821 : "Lookup webhook failed\n");
9822 0 : return 1;
9823 : }
9824 : /* Test webhook update */
9825 1 : cls->webhooks[0].webhook.event_type =
9826 : "Test paid";
9827 1 : cls->webhooks[0].webhook.url =
9828 : "example.com";
9829 1 : cls->webhooks[0].webhook.http_method =
9830 : "POST";
9831 1 : cls->webhooks[0].webhook.header_template =
9832 : "Authorization:WEKFOEKEXZ";
9833 1 : cls->webhooks[0].webhook.body_template =
9834 : "$Amount";
9835 1 : TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
9836 : &cls->webhooks[0],
9837 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9838 :
9839 1 : TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
9840 : &cls->webhooks[0]));
9841 1 : TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
9842 : &cls->webhooks[1],
9843 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9844 : /* Test collective webhook lookup */
9845 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9846 : &cls->webhooks[1],
9847 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9848 1 : TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
9849 : 2,
9850 : cls->webhooks));
9851 1 : TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance,
9852 : 2,
9853 : cls->webhooks));
9854 1 : TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
9855 : &cls->webhooks[2],
9856 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9857 :
9858 : /* Test webhook deletion */
9859 1 : TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance,
9860 : &cls->webhooks[1],
9861 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
9862 : /* Test double deletion fails */
9863 1 : TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance,
9864 : &cls->webhooks[1],
9865 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
9866 1 : cls->webhooks[1] = cls->webhooks[2];
9867 1 : TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
9868 : 2,
9869 : cls->webhooks));
9870 1 : TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance,
9871 : 2,
9872 : cls->webhooks));
9873 1 : return 0;
9874 : }
9875 :
9876 :
9877 : /**
9878 : * Takes care of webhook testing.
9879 : *
9880 : * @return 0 on success, 1 otherwise.
9881 : */
9882 : static int
9883 1 : test_webhooks (void)
9884 : {
9885 : struct TestWebhooks_Closure test_cls;
9886 : int test_result;
9887 :
9888 1 : pre_test_webhooks (&test_cls);
9889 1 : test_result = run_test_webhooks (&test_cls);
9890 1 : post_test_webhooks (&test_cls);
9891 1 : return test_result;
9892 : }
9893 :
9894 :
9895 : /* *********** Pending Webhooks ********** */
9896 :
9897 : /**
9898 : * A container for data relevant to a pending webhook.
9899 : */
9900 : struct PendingWebhookData
9901 : {
9902 : /**
9903 : * Reference to the configured webhook template.
9904 : */
9905 : uint64_t webhook_serial;
9906 :
9907 : /**
9908 : * The details of the pending webhook.
9909 : */
9910 : struct TALER_MERCHANTDB_PendingWebhookDetails pwebhook;
9911 : };
9912 :
9913 :
9914 : /**
9915 : * Creates a pending webhook for testing with.
9916 : *
9917 : * @param serial reference to the configured webhook template.
9918 : * @param pwebhook the pending webhook data to fill.
9919 : */
9920 : static void
9921 2 : make_pending_webhook (uint64_t webhook_serial,
9922 : struct PendingWebhookData *pwebhook)
9923 : {
9924 2 : pwebhook->webhook_serial = webhook_serial;
9925 2 : pwebhook->pwebhook.next_attempt = GNUNET_TIME_UNIT_ZERO_ABS;
9926 2 : pwebhook->pwebhook.retries = 0;
9927 2 : pwebhook->pwebhook.url = "https://exampletest.com";
9928 2 : pwebhook->pwebhook.http_method = "POST";
9929 2 : pwebhook->pwebhook.header = "Authorization:XYJAORKJEO";
9930 2 : pwebhook->pwebhook.body = "$Amount";
9931 2 : }
9932 :
9933 :
9934 : /**
9935 : * Tests inserting pending webhook data into the database.
9936 : *
9937 : * @param instance the instance to insert the pending webhook for.
9938 : * @param pending webhook the pending webhook data to insert.
9939 : * @param expected_result the result we expect the db to return.
9940 : * @return 0 when successful, 1 otherwise.
9941 : */
9942 : static int
9943 3 : test_insert_pending_webhook (const struct InstanceData *instance,
9944 : struct PendingWebhookData *pwebhook,
9945 : enum GNUNET_DB_QueryStatus expected_result)
9946 : {
9947 :
9948 3 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9949 2 : TEST_COND_RET_ON_FAIL (expected_result ==
9950 : TALER_MERCHANTDB_insert_pending_webhook (pg,
9951 : instance->instance.id,
9952 : pwebhook->
9953 : webhook_serial,
9954 : pwebhook->pwebhook.url,
9955 : pwebhook->pwebhook.
9956 : http_method,
9957 : pwebhook->pwebhook.
9958 : header,
9959 : pwebhook->pwebhook.body
9960 : ),
9961 : "Insert pending webhook failed\n");
9962 2 : return 0;
9963 : }
9964 :
9965 :
9966 : /**
9967 : * Tests updating pending webhook data in the database.
9968 : *
9969 : * @param instance the instance to update the pending webhook for.
9970 : * @param pending webhook the pending webhook data to update.
9971 : * @param expected_result the result we expect the db to return.
9972 : * @return 0 when successful, 1 otherwise.
9973 : */
9974 : static int
9975 2 : test_update_pending_webhook (const struct InstanceData *instance,
9976 : struct PendingWebhookData *pwebhook,
9977 : enum GNUNET_DB_QueryStatus expected_result)
9978 : {
9979 2 : pwebhook->pwebhook.next_attempt = GNUNET_TIME_relative_to_absolute (
9980 : GNUNET_TIME_UNIT_HOURS);
9981 2 : pwebhook->pwebhook.retries++;
9982 2 : TEST_SET_INSTANCE (instance->instance.id, expected_result);
9983 2 : TEST_COND_RET_ON_FAIL (expected_result ==
9984 : TALER_MERCHANTDB_update_pending_webhook (pg,
9985 : pwebhook->
9986 : webhook_serial,
9987 : pwebhook->pwebhook.
9988 : next_attempt),
9989 : "Update pending webhook failed\n");
9990 2 : return 0;
9991 : }
9992 :
9993 :
9994 : /**
9995 : * Container for information for looking up the row number of a deposit.
9996 : */
9997 : struct LookupPendingWebhookSerial_Closure
9998 : {
9999 : /**
10000 : * The pending webhook we're looking for.
10001 : */
10002 : const struct PendingWebhookData *pwebhook;
10003 :
10004 : /**
10005 : * The serial found.
10006 : */
10007 : uint64_t webhook_pending_serial;
10008 : };
10009 :
10010 :
10011 : /**
10012 : * Function called after calling @e test_lookup_all_webhook,
10013 : * test_iterate_pending_webhooks_next and test_lookup_pending_webhook
10014 : *
10015 : * @param cls a pointer to the lookup closure.
10016 : * @param webhook_serial reference to the configured webhook template.
10017 : */
10018 : static void
10019 4 : get_pending_serial_cb (void *cls,
10020 : uint64_t webhook_pending_serial,
10021 : struct GNUNET_TIME_Absolute next_attempt,
10022 : uint32_t retries,
10023 : const char *url,
10024 : const char *http_method,
10025 : const char *header,
10026 : const char *body)
10027 : {
10028 4 : struct LookupPendingWebhookSerial_Closure *lpw = cls;
10029 :
10030 4 : if ((0 == strcmp (lpw->pwebhook->pwebhook.url,
10031 2 : url)) &&
10032 2 : (0 == strcmp (lpw->pwebhook->pwebhook.http_method,
10033 2 : http_method)) &&
10034 2 : (0 == strcmp (lpw->pwebhook->pwebhook.header,
10035 2 : header)) &&
10036 2 : (0 == strcmp (lpw->pwebhook->pwebhook.body,
10037 : body)) )
10038 : {
10039 2 : lpw->webhook_pending_serial = webhook_pending_serial;
10040 : }
10041 : /* else
10042 : {
10043 : fprintf(stdout, "next_attempt: %lu vs %lu\n", lpw->pwebhook->pwebhook.next_attempt.abs_value_us, next_attempt.abs_value_us);
10044 : fprintf(stdout, "retries: %d vs %d\n", lpw->pwebhook->pwebhook.retries, retries);
10045 : fprintf(stdout, "url: %s vs %s\n", lpw->pwebhook->pwebhook.url, url);
10046 : fprintf(stdout, "http_method: %s vs %s\n", lpw->pwebhook->pwebhook.http_method, http_method);
10047 : fprintf(stdout, "header: %s vs %s\n", lpw->pwebhook->pwebhook.header, header);
10048 : fprintf(stdout, "body: %s vs %s\n", lpw->pwebhook->pwebhook.body, body);
10049 : }*/
10050 4 : }
10051 :
10052 :
10053 : /**
10054 : * Convenience function to retrieve the row number of a webhook pending in the database.
10055 : *
10056 : * @param instance the instance to get webhook pending(wp) from.
10057 : * @param webhook pending the wp to lookup the serial for.
10058 : * @return the row number of the deposit.
10059 : */
10060 : static uint64_t
10061 2 : get_pending_serial (const struct InstanceData *instance,
10062 : const struct PendingWebhookData *pwebhook)
10063 : {
10064 2 : struct LookupPendingWebhookSerial_Closure lpw = {
10065 : .pwebhook = pwebhook,
10066 : .webhook_pending_serial = 0
10067 : };
10068 :
10069 2 : GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
10070 : TALER_MERCHANTDB_set_instance (pg,
10071 : instance->instance.id));
10072 2 : GNUNET_assert (0 <
10073 : TALER_MERCHANTDB_iterate_pending_webhooks_above_serial_id (pg,
10074 : instance->instance.id,
10075 : 0,
10076 : INT_MAX,
10077 : &get_pending_serial_cb,
10078 : &lpw));
10079 2 : GNUNET_assert (0 != lpw.webhook_pending_serial);
10080 :
10081 2 : return lpw.webhook_pending_serial;
10082 : }
10083 :
10084 :
10085 : /**
10086 : * Closure for testing pending webhook lookup
10087 : */
10088 : struct TestLookupPendingWebhooks_Closure
10089 : {
10090 : /**
10091 : * Number of webhook serial to compare to
10092 : */
10093 : unsigned int webhooks_to_cmp_length;
10094 :
10095 : /**
10096 : * Pointer to array of webhook serials
10097 : */
10098 : const struct PendingWebhookData *webhooks_to_cmp;
10099 :
10100 : /**
10101 : * Pointer to array of number of matches for each pending webhook
10102 : */
10103 : unsigned int *results_matching;
10104 :
10105 : /**
10106 : * Total number of results returned
10107 : */
10108 : unsigned int results_length;
10109 : };
10110 :
10111 :
10112 : /**
10113 : * Function called after calling @e test_lookup_all_webhook,
10114 : * test_iterate_pending_webhooks_next and test_lookup_pending_webhook
10115 : *
10116 : * @param cls a pointer to the lookup closure.
10117 : * @param webhook_serial reference to the configured webhook template.
10118 : */
10119 : static void
10120 5 : lookup_pending_webhooks_cb (void *cls,
10121 : uint64_t webhook_pending_serial,
10122 : struct GNUNET_TIME_Absolute next_attempt,
10123 : uint32_t retries,
10124 : const char *url,
10125 : const char *http_method,
10126 : const char *header,
10127 : const char *body)
10128 : {
10129 5 : struct TestLookupPendingWebhooks_Closure *cmp = cls;
10130 :
10131 5 : cmp->results_length++;
10132 14 : for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
10133 : {
10134 9 : if ((0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.url,
10135 5 : url)) &&
10136 5 : (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.http_method,
10137 5 : http_method)) &&
10138 5 : (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.header,
10139 5 : header)) &&
10140 5 : (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.body,
10141 : body)) )
10142 : {
10143 5 : cmp->results_matching[i]++;
10144 : }
10145 : }
10146 5 : }
10147 :
10148 :
10149 : /**
10150 : * Tests looking up the pending webhook for an instance.
10151 : *
10152 : * @param instance the instance to query from.
10153 : * @param pwebhooks_length the number of pending webhook we are expecting.
10154 : * @param pwebhooks the list of pending webhooks that we expect to be found.
10155 : * @return 0 when successful, 1 otherwise.
10156 : */
10157 : static int
10158 1 : test_lookup_pending_webhooks (const struct InstanceData *instance,
10159 : unsigned int pwebhooks_length,
10160 : const struct PendingWebhookData *pwebhooks)
10161 1 : {
10162 1 : unsigned int results_matching[pwebhooks_length];
10163 1 : struct TestLookupPendingWebhooks_Closure cls = {
10164 : .webhooks_to_cmp_length = pwebhooks_length,
10165 : .webhooks_to_cmp = pwebhooks,
10166 : .results_matching = results_matching,
10167 : .results_length = 0
10168 : };
10169 :
10170 1 : memset (results_matching, 0, sizeof (results_matching));
10171 1 : if (0 > TALER_MERCHANTDB_iterate_pending_webhooks (pg,
10172 : 1024, /* limit */
10173 : &lookup_pending_webhooks_cb,
10174 : &cls))
10175 : {
10176 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10177 : "Lookup pending webhook failed\n");
10178 0 : return 1;
10179 : }
10180 1 : if (pwebhooks_length != cls.results_length)
10181 : {
10182 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10183 : "Lookup pending webhook failed: incorrect number of results\n");
10184 0 : return 1;
10185 : }
10186 3 : for (unsigned int i = 0; i < pwebhooks_length; i++)
10187 : {
10188 2 : if (1 != cls.results_matching[i])
10189 : {
10190 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10191 : "Lookup pending webhook failed: mismatched data\n");
10192 0 : return 1;
10193 : }
10194 : }
10195 1 : return 0;
10196 : }
10197 :
10198 :
10199 : /**
10200 : * Tests looking up the future webhook to send for an instance.
10201 : *
10202 : * @param instance the instance to query from.
10203 : * @param pwebhooks_length the number of pending webhook we are expecting.
10204 : * @param pwebhooks the list of pending webhooks that we expect to be found.
10205 : * @return 0 when successful, 1 otherwise.
10206 : */
10207 : static int
10208 1 : test_iterate_pending_webhooks_next (const struct InstanceData *instance,
10209 : unsigned int pwebhooks_length,
10210 : const struct PendingWebhookData *pwebhooks)
10211 1 : {
10212 1 : unsigned int results_matching[pwebhooks_length];
10213 1 : struct TestLookupPendingWebhooks_Closure cls = {
10214 : .webhooks_to_cmp_length = pwebhooks_length,
10215 : .webhooks_to_cmp = pwebhooks,
10216 : .results_matching = results_matching,
10217 : .results_length = 0
10218 : };
10219 :
10220 1 : memset (results_matching, 0, sizeof (results_matching));
10221 1 : if (0 > TALER_MERCHANTDB_iterate_pending_webhooks_next (pg,
10222 : &lookup_pending_webhooks_cb,
10223 : &cls))
10224 : {
10225 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10226 : "Lookup future webhook failed\n");
10227 0 : return 1;
10228 : }
10229 1 : if (pwebhooks_length != cls.results_length)
10230 : {
10231 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10232 : "Lookup future webhook failed: incorrect number of results\n");
10233 0 : return 1;
10234 : }
10235 2 : for (unsigned int i = 0; pwebhooks_length > i; ++i)
10236 : {
10237 1 : if (1 != cls.results_matching[i])
10238 : {
10239 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10240 : "Lookup future webhook failed: mismatched data\n");
10241 0 : return 1;
10242 : }
10243 : }
10244 1 : return 0;
10245 : }
10246 :
10247 :
10248 : /**
10249 : * Tests looking up all the pending webhook for an instance.
10250 : *
10251 : * @param instance the instance to query from.
10252 : * @param pwebhooks_length the number of pending webhook we are expecting.
10253 : * @param pwebhooks the list of pending webhooks that we expect to be found.
10254 : * @return 0 when successful, 1 otherwise.
10255 : */
10256 : static int
10257 2 : test_lookup_all_webhooks (const struct InstanceData *instance,
10258 : unsigned int pwebhooks_length,
10259 : const struct PendingWebhookData *pwebhooks)
10260 2 : {
10261 2 : uint64_t max_results = 2;
10262 2 : uint64_t min_row = 0;
10263 2 : unsigned int results_matching[GNUNET_NZL (pwebhooks_length)];
10264 2 : struct TestLookupPendingWebhooks_Closure cls = {
10265 : .webhooks_to_cmp_length = pwebhooks_length,
10266 : .webhooks_to_cmp = pwebhooks,
10267 : .results_matching = results_matching,
10268 : .results_length = 0
10269 : };
10270 :
10271 2 : memset (results_matching,
10272 : 0,
10273 : sizeof (results_matching));
10274 2 : TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10275 2 : if (0 > TALER_MERCHANTDB_iterate_pending_webhooks_above_serial_id (pg,
10276 2 : instance->instance.id,
10277 : min_row,
10278 : max_results,
10279 : &lookup_pending_webhooks_cb,
10280 : &cls))
10281 : {
10282 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10283 : "Lookup all webhooks failed\n");
10284 0 : return 1;
10285 : }
10286 2 : if (pwebhooks_length != cls.results_length)
10287 : {
10288 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10289 : "Lookup all webhooks failed: incorrect number of results\n");
10290 0 : return 1;
10291 : }
10292 4 : for (unsigned int i = 0; pwebhooks_length > i; ++i)
10293 : {
10294 2 : if (1 != cls.results_matching[i])
10295 : {
10296 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
10297 : "Lookup all webhooks failed: mismatched data\n");
10298 0 : return 1;
10299 : }
10300 : }
10301 2 : return 0;
10302 : }
10303 :
10304 :
10305 : /**
10306 : * Tests deleting a pending webhook.
10307 : *
10308 : * @param instance the instance to delete the pending webhook from.
10309 : * @param pwebhook the pending webhook that should be deleted.
10310 : * @param expected_result the result that we expect the DB to return.
10311 : * @return 0 when successful, 1 otherwise.
10312 : */
10313 : static int
10314 3 : test_delete_pending_webhook (uint64_t webhooks_pending_serial,
10315 : enum GNUNET_DB_QueryStatus expected_result)
10316 : {
10317 :
10318 3 : TEST_COND_RET_ON_FAIL (expected_result ==
10319 : TALER_MERCHANTDB_delete_pending_webhook (pg,
10320 : webhooks_pending_serial),
10321 : "Delete webhook failed\n");
10322 3 : return 0;
10323 : }
10324 :
10325 :
10326 : /**
10327 : * Closure for pending webhook tests.
10328 : */
10329 : struct TestPendingWebhooks_Closure
10330 : {
10331 : /**
10332 : * The instance to use for this test.
10333 : */
10334 : struct InstanceData instance;
10335 :
10336 : /**
10337 : * Webhooks that the pending webhooks reference via foreign key.
10338 : * Each per-instance schema has its own webhook_serial sequence
10339 : * starting at 1, so we must populate webhooks before inserting
10340 : * pending webhooks.
10341 : */
10342 : struct WebhookData webhooks[4];
10343 :
10344 : /**
10345 : * The array of pending webhooks.
10346 : */
10347 : struct PendingWebhookData pwebhooks[2];
10348 : };
10349 :
10350 :
10351 : /**
10352 : * Sets up the data structures used in the pending webhook tests.
10353 : *
10354 : * @param cls the closure to fill with test data.
10355 : */
10356 : static void
10357 1 : pre_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
10358 : {
10359 : /* Instance */
10360 1 : make_instance ("test_inst_pending_webhooks",
10361 : &cls->instance);
10362 :
10363 : /* Webhooks the pending webhooks reference (serials 1..4) */
10364 1 : make_webhook ("test_pwh_wb_0", &cls->webhooks[0]);
10365 1 : make_webhook ("test_pwh_wb_1", &cls->webhooks[1]);
10366 1 : make_webhook ("test_pwh_wb_2", &cls->webhooks[2]);
10367 1 : make_webhook ("test_pwh_wb_3", &cls->webhooks[3]);
10368 :
10369 : /* Webhooks */
10370 1 : make_pending_webhook (1,
10371 : &cls->pwebhooks[0]);
10372 :
10373 1 : make_pending_webhook (4,
10374 : &cls->pwebhooks[1]);
10375 1 : cls->pwebhooks[1].pwebhook.url = "https://test.com";
10376 1 : cls->pwebhooks[1].pwebhook.http_method = "POST";
10377 1 : cls->pwebhooks[1].pwebhook.header = "Authorization:XYJAO5R06EO";
10378 1 : cls->pwebhooks[1].pwebhook.body = "$Amount";
10379 1 : }
10380 :
10381 :
10382 : /**
10383 : * Handles all teardown after testing.
10384 : *
10385 : * @param cls the closure containing memory to be freed.
10386 : */
10387 : static void
10388 1 : post_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
10389 : {
10390 1 : free_instance_data (&cls->instance);
10391 1 : }
10392 :
10393 :
10394 : /**
10395 : * Runs the tests for pending webhooks.
10396 : *
10397 : * @param cls the container of the test data.
10398 : * @return 0 on success, 1 otherwise.
10399 : */
10400 : static int
10401 1 : run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
10402 : {
10403 : uint64_t webhook_pending_serial0;
10404 : uint64_t webhook_pending_serial1;
10405 :
10406 : /* Test that insert without an instance fails */
10407 1 : TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
10408 : &cls->pwebhooks[0],
10409 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
10410 :
10411 : /* Insert the instance */
10412 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
10413 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10414 :
10415 : /* Insert the referenced webhooks so pending webhook FKs resolve. */
10416 5 : for (unsigned int i = 0; i < 4; i++)
10417 4 : TEST_RET_ON_FAIL (
10418 : test_insert_webhook (&cls->instance,
10419 : &cls->webhooks[i],
10420 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10421 :
10422 : /* Test inserting a pending webhook */
10423 1 : TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
10424 : &cls->pwebhooks[0],
10425 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10426 1 : TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
10427 : &cls->pwebhooks[1],
10428 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10429 : /* Test collective pending webhook lookup */
10430 1 : TEST_RET_ON_FAIL (test_lookup_pending_webhooks (&cls->instance,
10431 : 2,
10432 : cls->pwebhooks));
10433 : /* Test pending webhook update */
10434 1 : TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
10435 : &cls->pwebhooks[0],
10436 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10437 1 : TEST_RET_ON_FAIL (test_iterate_pending_webhooks_next (&cls->instance,
10438 : 1,
10439 : &cls->pwebhooks[1]));
10440 1 : TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
10441 : &cls->pwebhooks[1],
10442 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
10443 : // ???
10444 1 : TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
10445 : 2,
10446 : cls->pwebhooks));
10447 :
10448 1 : webhook_pending_serial0 = get_pending_serial (&cls->instance,
10449 1 : &cls->pwebhooks[0]);
10450 1 : webhook_pending_serial1 = get_pending_serial (&cls->instance,
10451 1 : &cls->pwebhooks[1]);
10452 :
10453 : /* Test webhook deletion */
10454 1 : TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
10455 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10456 : /* Test double deletion fails */
10457 1 : TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
10458 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
10459 1 : TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial0,
10460 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
10461 1 : TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
10462 : 0,
10463 : NULL));
10464 1 : return 0;
10465 : }
10466 :
10467 :
10468 : /**
10469 : * Takes care of pending webhook testing.
10470 : *
10471 : * @return 0 on success, 1 otherwise.
10472 : */
10473 : static int
10474 1 : test_pending_webhooks (void)
10475 : {
10476 : struct TestPendingWebhooks_Closure test_cls;
10477 : int test_result;
10478 :
10479 1 : pre_test_pending_webhooks (&test_cls);
10480 1 : test_result = run_test_pending_webhooks (&test_cls);
10481 1 : post_test_pending_webhooks (&test_cls);
10482 1 : return test_result;
10483 : }
10484 :
10485 :
10486 : /* ********** Statistics ********** */
10487 :
10488 :
10489 : /**
10490 : * Closure for the statistics tests.
10491 : */
10492 : struct TestStatistics_Closure
10493 : {
10494 : /**
10495 : * Instance the statistics are collected for.
10496 : */
10497 : struct InstanceData instance;
10498 : };
10499 :
10500 :
10501 : /**
10502 : * Closure for #count_bucket_counter_cb().
10503 : */
10504 : struct CountBuckets_Closure
10505 : {
10506 : /**
10507 : * Number of bucket rows we were called with.
10508 : */
10509 : unsigned int count;
10510 : };
10511 :
10512 :
10513 : /**
10514 : * Runs @a sql directly on the database connection of the test. Used to
10515 : * set up statistics state that has no dedicated MERCHANTDB entry point
10516 : * (the statistics tables are only ever written by triggers).
10517 : *
10518 : * @param sql the SQL statement(s) to execute
10519 : * @return 0 on success, 1 otherwise.
10520 : */
10521 : static int
10522 12 : statistics_exec_sql (const char *sql)
10523 : {
10524 12 : struct GNUNET_PQ_ExecuteStatement es[] = {
10525 12 : GNUNET_PQ_make_execute (sql),
10526 : GNUNET_PQ_EXECUTE_STATEMENT_END
10527 : };
10528 :
10529 12 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10530 : GNUNET_PQ_exec_statements (pg->conn,
10531 : es),
10532 : "Direct SQL execution failed\n");
10533 12 : return 0;
10534 : }
10535 :
10536 :
10537 : /**
10538 : * Counts the bucket rows we are called for.
10539 : *
10540 : * @param cls a `struct CountBuckets_Closure *`
10541 : * @param description description of the statistic
10542 : * @param bucket_start start time of the bucket
10543 : * @param bucket_end end time of the bucket
10544 : * @param bucket_range range of the bucket
10545 : * @param cumulative_counter counter value
10546 : */
10547 : static void
10548 13 : count_bucket_counter_cb (void *cls,
10549 : const char *description,
10550 : struct GNUNET_TIME_Timestamp bucket_start,
10551 : struct GNUNET_TIME_Timestamp bucket_end,
10552 : const char *bucket_range,
10553 : uint64_t cumulative_counter)
10554 : {
10555 13 : struct CountBuckets_Closure *cbc = cls;
10556 :
10557 : (void) description;
10558 : (void) bucket_start;
10559 : (void) bucket_end;
10560 : (void) bucket_range;
10561 : (void) cumulative_counter;
10562 13 : cbc->count++;
10563 13 : }
10564 :
10565 :
10566 : /**
10567 : * Determines how many bucket rows exist for @a slug.
10568 : *
10569 : * @param instance the instance to look at.
10570 : * @param slug the statistic to count the buckets of.
10571 : * @param[out] count set to the number of bucket rows.
10572 : * @return 0 on success, 1 otherwise.
10573 : */
10574 : static int
10575 3 : count_bucket_counters (const struct InstanceData *instance,
10576 : const char *slug,
10577 : unsigned int *count)
10578 : {
10579 3 : struct CountBuckets_Closure cbc = {
10580 : .count = 0
10581 : };
10582 :
10583 3 : TEST_SET_INSTANCE (instance->instance.id,
10584 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10585 3 : TEST_COND_RET_ON_FAIL (0 <=
10586 : TALER_MERCHANTDB_iterate_statistic_bucket_counters (
10587 : pg,
10588 : instance->instance.id,
10589 : slug,
10590 : &count_bucket_counter_cb,
10591 : &cbc),
10592 : "Lookup of bucket statistics failed\n");
10593 3 : *count = cbc.count;
10594 3 : return 0;
10595 : }
10596 :
10597 :
10598 : /**
10599 : * Tests that garbage collection does not throw away bucket statistics
10600 : * that are well within their retention age. Regression test: the
10601 : * retention cut-off in merchant_statistic_bucket_gc() used to be
10602 : * computed with EXTRACT(SECONDS FROM ...), which is the seconds *field*
10603 : * of the interval and thus zero for every range we use, so the cut-off
10604 : * degenerated to "now" and every bucket row was deleted on every run.
10605 : *
10606 : * @param instance the instance to collect statistics for.
10607 : * @return 0 on success, 1 otherwise.
10608 : */
10609 : static int
10610 1 : test_statistics_bucket_gc (const struct InstanceData *instance)
10611 : {
10612 : unsigned int before;
10613 : unsigned int after;
10614 :
10615 1 : TEST_SET_INSTANCE (instance->instance.id,
10616 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10617 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10618 : "CALL merchant_do_bump_number_stat"
10619 : " ('tokens-issued'"
10620 : " ,CURRENT_TIMESTAMP(0)::TIMESTAMP"
10621 : " ,1)"));
10622 1 : TEST_RET_ON_FAIL (count_bucket_counters (instance,
10623 : "tokens-issued",
10624 : &before));
10625 1 : TEST_COND_RET_ON_FAIL (0 < before,
10626 : "Bumping a bucket statistic recorded nothing\n");
10627 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10628 : TALER_MERCHANTDB_gc (pg),
10629 : "Garbage collection failed\n");
10630 1 : TEST_RET_ON_FAIL (count_bucket_counters (instance,
10631 : "tokens-issued",
10632 : &after));
10633 1 : TEST_COND_RET_ON_FAIL (before == after,
10634 : "Garbage collection deleted bucket statistics\n");
10635 1 : return 0;
10636 : }
10637 :
10638 :
10639 : /**
10640 : * Counts the amount buckets we are called for.
10641 : *
10642 : * @param cls a `struct CountBuckets_Closure *`
10643 : * @param description description of the statistic
10644 : * @param bucket_start start time of the bucket
10645 : * @param bucket_end end time of the bucket
10646 : * @param bucket_range range of the bucket
10647 : * @param cumulative_amounts_len length of @a cumulative_amounts
10648 : * @param cumulative_amounts the cumulative amounts
10649 : */
10650 : static void
10651 36 : count_bucket_amount_cb (void *cls,
10652 : const char *description,
10653 : struct GNUNET_TIME_Timestamp bucket_start,
10654 : struct GNUNET_TIME_Timestamp bucket_end,
10655 : const char *bucket_range,
10656 : unsigned int cumulative_amounts_len,
10657 : const struct TALER_Amount cumulative_amounts[static
10658 : cumulative_amounts_len])
10659 36 : {
10660 36 : struct CountBuckets_Closure *cbc = cls;
10661 :
10662 : (void) description;
10663 : (void) bucket_start;
10664 : (void) bucket_end;
10665 : (void) bucket_range;
10666 : (void) cumulative_amounts_len;
10667 : (void) cumulative_amounts;
10668 36 : cbc->count++;
10669 36 : }
10670 :
10671 :
10672 : /**
10673 : * Bumps the amount statistic @a slug and checks that the bump was
10674 : * actually recorded in the bucket statistics. Fails if @a slug is not
10675 : * registered in @a instance, as the bump procedures silently ignore
10676 : * statistics nobody registered.
10677 : *
10678 : * @param instance the instance to collect statistics for.
10679 : * @param slug the statistic to bump.
10680 : * @return 0 on success, 1 otherwise.
10681 : */
10682 : static int
10683 6 : test_statistics_amount_slug_collected (const struct InstanceData *instance,
10684 : const char *slug)
10685 : {
10686 6 : struct CountBuckets_Closure cbc = {
10687 : .count = 0
10688 : };
10689 : char *sql;
10690 :
10691 6 : TEST_SET_INSTANCE (instance->instance.id,
10692 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10693 6 : GNUNET_asprintf (&sql,
10694 : "CALL merchant_do_bump_amount_stat"
10695 : " ('%s'"
10696 : " ,CURRENT_TIMESTAMP(0)::TIMESTAMP"
10697 : " ,ROW(5,0,'EUR')::merchant.taler_amount_currency)",
10698 : slug);
10699 : {
10700 : int ret;
10701 :
10702 6 : ret = statistics_exec_sql (sql);
10703 6 : GNUNET_free (sql);
10704 6 : TEST_RET_ON_FAIL (ret);
10705 : }
10706 6 : TEST_COND_RET_ON_FAIL (0 <=
10707 : TALER_MERCHANTDB_iterate_statistic_bucket_amounts (
10708 : pg,
10709 : instance->instance.id,
10710 : slug,
10711 : &count_bucket_amount_cb,
10712 : &cbc),
10713 : "Lookup of bucket statistics failed\n");
10714 6 : TEST_COND_RET_ON_FAIL (0 < cbc.count,
10715 : "Statistic bumped by a trigger is not registered\n");
10716 6 : return 0;
10717 : }
10718 :
10719 :
10720 : /**
10721 : * Tests that bumping a statistic only uses the bucket ranges of the
10722 : * registration with the matching type. Regression test: the cursor
10723 : * over the ranges did not filter on the type, so a slug registered
10724 : * both as a number and as an amount statistic got buckets for the
10725 : * union of both range sets.
10726 : *
10727 : * @param instance the instance to collect statistics for.
10728 : * @return 0 on success, 1 otherwise.
10729 : */
10730 : static int
10731 1 : test_statistics_dual_type_slug (const struct InstanceData *instance)
10732 : {
10733 : unsigned int count;
10734 :
10735 1 : TEST_SET_INSTANCE (instance->instance.id,
10736 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10737 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10738 : "INSERT INTO merchant_statistic_bucket_meta"
10739 : " (slug"
10740 : " ,description"
10741 : " ,stype"
10742 : " ,ranges"
10743 : " ,ages)"
10744 : " VALUES"
10745 : " ('test-dual-type'"
10746 : " ,'registered as a number statistic'"
10747 : " ,'number'"
10748 : " ,ARRAY['hour'::merchant.statistic_range]"
10749 : " ,ARRAY[72])"
10750 : " ,('test-dual-type'"
10751 : " ,'registered as an amount statistic'"
10752 : " ,'amount'"
10753 : " ,ARRAY['day'::merchant.statistic_range,'week','month']"
10754 : " ,ARRAY[14,12,24])"));
10755 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10756 : "CALL merchant_do_bump_number_stat"
10757 : " ('test-dual-type'"
10758 : " ,CURRENT_TIMESTAMP(0)::TIMESTAMP"
10759 : " ,1)"));
10760 1 : TEST_RET_ON_FAIL (count_bucket_counters (instance,
10761 : "test-dual-type",
10762 : &count));
10763 1 : TEST_COND_RET_ON_FAIL (1 == count,
10764 : "Bump used the ranges of the wrong registration\n");
10765 1 : return 0;
10766 : }
10767 :
10768 :
10769 : /**
10770 : * Determines how many interval events are stored for @a slug.
10771 : *
10772 : * @param instance the instance to look at.
10773 : * @param slug the statistic to count the events of.
10774 : * @param[out] count set to the number of events.
10775 : * @return 0 on success, 1 otherwise.
10776 : */
10777 : static int
10778 1 : statistics_count_counter_events (const struct InstanceData *instance,
10779 : const char *slug,
10780 : uint64_t *count)
10781 : {
10782 1 : struct GNUNET_PQ_QueryParam params[] = {
10783 1 : GNUNET_PQ_query_param_string (slug),
10784 : GNUNET_PQ_query_param_end
10785 : };
10786 1 : struct GNUNET_PQ_ResultSpec rs[] = {
10787 1 : GNUNET_PQ_result_spec_uint64 ("num",
10788 : count),
10789 : GNUNET_PQ_result_spec_end
10790 : };
10791 :
10792 1 : TEST_SET_INSTANCE (instance->instance.id,
10793 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10794 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10795 : GNUNET_PQ_prepare_anon (
10796 : pg->conn,
10797 : "SELECT COUNT(*) AS num"
10798 : " FROM merchant_statistic_counter_event"
10799 : " WHERE imeta_serial_id ="
10800 : " (SELECT imeta_serial_id"
10801 : " FROM merchant_statistic_interval_meta"
10802 : " WHERE slug=$1"
10803 : " AND stype='number')"),
10804 : "Preparing event count failed\n");
10805 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
10806 : GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
10807 : "",
10808 : params,
10809 : rs),
10810 : "Counting interval events failed\n");
10811 1 : return 0;
10812 : }
10813 :
10814 :
10815 : /**
10816 : * Tests that garbage collection survives compacting an interval event
10817 : * that an interval counter still uses as its watermark. Regression
10818 : * test: merchant_statistic_counter_gc() deletes all but the lowest
10819 : * serial of a window, which trips the ON DELETE RESTRICT foreign key
10820 : * of merchant_statistic_interval_counter.event_delimiter as soon as
10821 : * the watermark is not that lowest serial. merchant_do_gc() does not
10822 : * trap that error, so garbage collection was abandoned for all
10823 : * remaining instances.
10824 : *
10825 : * @param instance the instance to collect statistics for.
10826 : * @return 0 on success, 1 otherwise.
10827 : */
10828 : static int
10829 1 : test_statistics_counter_gc_delimiter (const struct InstanceData *instance)
10830 : {
10831 1 : TEST_SET_INSTANCE (instance->instance.id,
10832 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10833 : /* Two events in the same coarsening window, with the watermark of the
10834 : interval counter on the younger (higher serial) one. */
10835 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10836 : "DO $$\n"
10837 : "DECLARE\n"
10838 : " my_time INT8 := ROUND(EXTRACT(epoch FROM"
10839 : " CURRENT_TIMESTAMP(0)::TIMESTAMP))::INT8;\n"
10840 : " my_meta INT8;\n"
10841 : " my_first INT8;\n"
10842 : " my_second INT8;\n"
10843 : "BEGIN\n"
10844 : " SELECT imeta_serial_id\n"
10845 : " INTO my_meta\n"
10846 : " FROM merchant_statistic_interval_meta\n"
10847 : " WHERE slug='orders-created'\n"
10848 : " AND stype='number';\n"
10849 : " INSERT INTO merchant_statistic_counter_event\n"
10850 : " (imeta_serial_id, slot, delta)\n"
10851 : " VALUES (my_meta, my_time - 7000, 1)\n"
10852 : " RETURNING nevent_serial_id INTO my_first;\n"
10853 : " INSERT INTO merchant_statistic_counter_event\n"
10854 : " (imeta_serial_id, slot, delta)\n"
10855 : " VALUES (my_meta, my_time - 6900, 1)\n"
10856 : " RETURNING nevent_serial_id INTO my_second;\n"
10857 : " INSERT INTO merchant_statistic_interval_counter\n"
10858 : " (imeta_serial_id, range, event_delimiter,"
10859 : " cumulative_number)\n"
10860 : " VALUES (my_meta, 7200, my_second, 1);\n"
10861 : "END $$;"));
10862 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10863 : TALER_MERCHANTDB_gc (pg),
10864 : "Garbage collection failed\n");
10865 : {
10866 : uint64_t num_events;
10867 :
10868 1 : TEST_RET_ON_FAIL (statistics_count_counter_events (instance,
10869 : "orders-created",
10870 : &num_events));
10871 1 : TEST_COND_RET_ON_FAIL (1 == num_events,
10872 : "Garbage collection did not compact the events\n");
10873 : }
10874 1 : return 0;
10875 : }
10876 :
10877 :
10878 : /**
10879 : * SQL bumping the 'tokens-used' statistic by one.
10880 : */
10881 : #define BUMP_TOKENS_USED \
10882 : "CALL merchant_do_bump_number_stat" \
10883 : " ('tokens-used'" \
10884 : " ,CURRENT_TIMESTAMP(0)::TIMESTAMP" \
10885 : " ,1)"
10886 :
10887 :
10888 : /**
10889 : * Determines the largest counter value stored for @a slug.
10890 : *
10891 : * @param instance the instance to look at.
10892 : * @param slug the statistic to look at.
10893 : * @param[out] value set to the largest bucket counter.
10894 : * @return 0 on success, 1 otherwise.
10895 : */
10896 : static int
10897 1 : statistics_max_bucket_counter (const struct InstanceData *instance,
10898 : const char *slug,
10899 : uint64_t *value)
10900 : {
10901 1 : struct GNUNET_PQ_QueryParam params[] = {
10902 1 : GNUNET_PQ_query_param_string (slug),
10903 : GNUNET_PQ_query_param_end
10904 : };
10905 1 : struct GNUNET_PQ_ResultSpec rs[] = {
10906 1 : GNUNET_PQ_result_spec_uint64 ("num",
10907 : value),
10908 : GNUNET_PQ_result_spec_end
10909 : };
10910 :
10911 1 : TEST_SET_INSTANCE (instance->instance.id,
10912 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10913 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10914 : GNUNET_PQ_prepare_anon (
10915 : pg->conn,
10916 : "SELECT COALESCE(MAX(cumulative_number),0) AS num"
10917 : " FROM merchant_statistic_bucket_counter"
10918 : " WHERE bmeta_serial_id ="
10919 : " (SELECT bmeta_serial_id"
10920 : " FROM merchant_statistic_bucket_meta"
10921 : " WHERE slug=$1"
10922 : " AND stype='number')"),
10923 : "Preparing counter lookup failed\n");
10924 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
10925 : GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
10926 : "",
10927 : params,
10928 : rs),
10929 : "Looking up bucket counter failed\n");
10930 1 : return 0;
10931 : }
10932 :
10933 :
10934 : /**
10935 : * Bumps the 'tokens-used' statistic from a second process while this
10936 : * process still holds the very first (uncommitted) bump of the same
10937 : * bucket. Regression test: the bucket statistics used to be written
10938 : * as UPDATE-then-INSERT, so the second writer got a unique violation
10939 : * (which GNUnet maps to SUCCESS_NO_RESULTS) and its event was lost;
10940 : * in the pay path the very same pattern surfaces to the wallet as a
10941 : * bogus 409 on a perfectly valid payment.
10942 : *
10943 : * @param instance the instance to collect statistics for.
10944 : * @return 0 on success, 1 otherwise.
10945 : */
10946 : static int
10947 1 : test_statistics_bucket_upsert_race (const struct InstanceData *instance)
10948 : {
10949 : pid_t child;
10950 : int status;
10951 : uint64_t value;
10952 :
10953 1 : TEST_SET_INSTANCE (instance->instance.id,
10954 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT);
10955 : /* Make sure the bucket does not exist yet, only then do both writers
10956 : race to create it. */
10957 1 : TEST_RET_ON_FAIL (statistics_exec_sql (
10958 : "DELETE FROM merchant_statistic_bucket_counter"
10959 : " WHERE bmeta_serial_id ="
10960 : " (SELECT bmeta_serial_id"
10961 : " FROM merchant_statistic_bucket_meta"
10962 : " WHERE slug='tokens-used'"
10963 : " AND stype='number')"));
10964 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
10965 : TALER_MERCHANTDB_start_read_committed (
10966 : pg,
10967 : "bucket statistics upsert race"),
10968 : "Failed to start transaction\n");
10969 1 : TEST_RET_ON_FAIL (statistics_exec_sql (BUMP_TOKENS_USED));
10970 1 : child = fork ();
10971 1 : if (-1 == child)
10972 : {
10973 0 : TALER_MERCHANTDB_rollback (pg);
10974 0 : GNUNET_break (0);
10975 0 : return 1;
10976 : }
10977 1 : if (0 == child)
10978 : {
10979 : struct TALER_MERCHANTDB_PostgresContext *cpg;
10980 0 : int ret = 1;
10981 :
10982 : /* Second writer: blocks on the uncommitted row of the parent until
10983 : the parent commits below. */
10984 0 : cpg = TALER_MERCHANTDB_connect (test_cfg);
10985 0 : if ( (NULL != cpg) &&
10986 : (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
10987 0 : TALER_MERCHANTDB_set_instance (cpg,
10988 0 : instance->instance.id)) )
10989 : {
10990 0 : struct GNUNET_PQ_ExecuteStatement es[] = {
10991 0 : GNUNET_PQ_make_execute (BUMP_TOKENS_USED),
10992 : GNUNET_PQ_EXECUTE_STATEMENT_END
10993 : };
10994 :
10995 0 : if (GNUNET_OK ==
10996 0 : GNUNET_PQ_exec_statements (cpg->conn,
10997 : es))
10998 0 : ret = 0;
10999 : }
11000 0 : _exit (ret);
11001 : }
11002 1 : sleep (1); /* give the child time to reach the conflicting INSERT */
11003 1 : TEST_COND_RET_ON_FAIL (0 <=
11004 : TALER_MERCHANTDB_commit (pg),
11005 : "Failed to commit transaction\n");
11006 1 : if (0 >= waitpid (child,
11007 : &status,
11008 : 0))
11009 0 : sleep (2); /* cannot reap the child, just give it time to finish */
11010 1 : TEST_RET_ON_FAIL (statistics_max_bucket_counter (instance,
11011 : "tokens-used",
11012 : &value));
11013 1 : TEST_COND_RET_ON_FAIL (2 == value,
11014 : "Concurrent statistics bump was lost\n");
11015 1 : return 0;
11016 : }
11017 :
11018 :
11019 : /**
11020 : * Prepares for the statistics tests.
11021 : *
11022 : * @param[out] cls the closure to initialize.
11023 : */
11024 : static void
11025 1 : pre_test_statistics (struct TestStatistics_Closure *cls)
11026 : {
11027 1 : make_instance ("test_inst_statistics",
11028 : &cls->instance);
11029 1 : }
11030 :
11031 :
11032 : /**
11033 : * Cleans up after the statistics tests.
11034 : *
11035 : * @param[in,out] cls the closure to clean up.
11036 : */
11037 : static void
11038 1 : post_test_statistics (struct TestStatistics_Closure *cls)
11039 : {
11040 1 : free_instance_data (&cls->instance);
11041 1 : }
11042 :
11043 :
11044 : /**
11045 : * Runs the statistics tests.
11046 : *
11047 : * @param cls the closure to use.
11048 : * @return 0 on success, 1 otherwise.
11049 : */
11050 : static int
11051 1 : run_test_statistics (struct TestStatistics_Closure *cls)
11052 : {
11053 1 : TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
11054 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
11055 1 : TEST_RET_ON_FAIL (test_statistics_bucket_gc (&cls->instance));
11056 : /* Every amount statistic bumped by a trigger in pg_triggers.sql must
11057 : be registered in the per-instance schema. */
11058 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
11059 : &cls->instance,
11060 : "payments-received-after-deposit-fee"));
11061 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
11062 : &cls->instance,
11063 : "total-deposit-fees-paid"));
11064 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
11065 : &cls->instance,
11066 : "total-wire-fees-paid"));
11067 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
11068 : &cls->instance,
11069 : "refunds-granted"));
11070 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
11071 : &cls->instance,
11072 : "deposits-received"));
11073 1 : TEST_RET_ON_FAIL (test_statistics_amount_slug_collected (
11074 : &cls->instance,
11075 : "deposits-fees-paid"));
11076 1 : TEST_RET_ON_FAIL (test_statistics_counter_gc_delimiter (&cls->instance));
11077 1 : TEST_RET_ON_FAIL (test_statistics_dual_type_slug (&cls->instance));
11078 1 : TEST_RET_ON_FAIL (test_statistics_bucket_upsert_race (&cls->instance));
11079 1 : return 0;
11080 : }
11081 :
11082 :
11083 : /**
11084 : * Takes care of statistics testing.
11085 : *
11086 : * @return 0 on success, 1 otherwise.
11087 : */
11088 : static int
11089 1 : test_statistics (void)
11090 : {
11091 : struct TestStatistics_Closure test_cls;
11092 : int test_result;
11093 :
11094 1 : pre_test_statistics (&test_cls);
11095 1 : test_result = run_test_statistics (&test_cls);
11096 1 : post_test_statistics (&test_cls);
11097 1 : return test_result;
11098 : }
11099 :
11100 :
11101 : /* ********** MFA challenges ********** */
11102 :
11103 :
11104 : /**
11105 : * Tests solving multi-factor authentication (MFA) challenges.
11106 : *
11107 : * Besides the obvious cases, this pins down the *idempotency* of
11108 : * confirming a challenge that has already been confirmed: any TAN is
11109 : * then accepted and the retry counter is left alone. That is
11110 : * intentional (see the comment in do_solve_mfa_challenge.sql): the
11111 : * authorization is carried by the confirmation already stored in the
11112 : * database, which this path does not change, and reaching it already
11113 : * requires knowing the challenge ID together with the matching body
11114 : * hash. The assertions are here so that the contract cannot silently
11115 : * drift.
11116 : *
11117 : * @return 0 on success, 1 otherwise.
11118 : */
11119 : static int
11120 1 : test_mfa_challenges (void)
11121 : {
11122 : struct TALER_MERCHANT_MFA_BodyHash h_body;
11123 : struct TALER_MERCHANT_MFA_BodySalt salt;
11124 : struct TALER_MERCHANT_MFA_BodyHash other_h_body;
11125 : uint64_t challenge_id;
11126 : bool solved;
11127 : uint32_t retry_counter;
11128 :
11129 1 : GNUNET_CRYPTO_random_block (&h_body,
11130 : sizeof (h_body));
11131 1 : GNUNET_CRYPTO_random_block (&other_h_body,
11132 : sizeof (other_h_body));
11133 1 : GNUNET_CRYPTO_random_block (&salt,
11134 : sizeof (salt));
11135 1 : TEST_COND_RET_ON_FAIL (
11136 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
11137 : TALER_MERCHANTDB_insert_mfa_challenge (
11138 : pg,
11139 : "test_inst_mfa",
11140 : TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION,
11141 : &h_body,
11142 : &salt,
11143 : "1234-5678",
11144 : GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS),
11145 : GNUNET_TIME_UNIT_ZERO_ABS,
11146 : TALER_MERCHANT_MFA_CHANNEL_EMAIL,
11147 : "test@example.com",
11148 : &challenge_id),
11149 : "Insert MFA challenge failed\n");
11150 : /* A challenge is only addressable with the matching body hash. */
11151 1 : TEST_COND_RET_ON_FAIL (
11152 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
11153 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11154 : challenge_id,
11155 : &other_h_body,
11156 : "1234-5678",
11157 : &solved,
11158 : &retry_counter),
11159 : "Challenge was solvable with a different body hash\n");
11160 1 : TEST_COND_RET_ON_FAIL (! solved,
11161 : "Challenge solved with a different body hash\n");
11162 : /* A wrong TAN is rejected and costs one of the three attempts. */
11163 1 : TEST_COND_RET_ON_FAIL (
11164 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
11165 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11166 : challenge_id,
11167 : &h_body,
11168 : "0000-0000",
11169 : &solved,
11170 : &retry_counter),
11171 : "Solving MFA challenge failed\n");
11172 1 : TEST_COND_RET_ON_FAIL (! solved,
11173 : "A wrong TAN solved the challenge\n");
11174 1 : TEST_COND_RET_ON_FAIL (2 == retry_counter,
11175 : "A wrong TAN did not cost an attempt\n");
11176 : /* The correct TAN confirms the challenge. */
11177 1 : TEST_COND_RET_ON_FAIL (
11178 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
11179 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11180 : challenge_id,
11181 : &h_body,
11182 : "1234-5678",
11183 : &solved,
11184 : &retry_counter),
11185 : "Solving MFA challenge failed\n");
11186 1 : TEST_COND_RET_ON_FAIL (solved,
11187 : "The correct TAN did not solve the challenge\n");
11188 1 : TEST_COND_RET_ON_FAIL (2 == retry_counter,
11189 : "The correct TAN cost an attempt\n");
11190 : /* Re-confirming an already confirmed challenge is idempotent. */
11191 1 : TEST_COND_RET_ON_FAIL (
11192 : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
11193 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11194 : challenge_id,
11195 : &h_body,
11196 : "0000-0000",
11197 : &solved,
11198 : &retry_counter),
11199 : "Solving MFA challenge failed\n");
11200 1 : TEST_COND_RET_ON_FAIL (solved,
11201 : "Re-confirming a confirmed challenge is not"
11202 : " idempotent\n");
11203 1 : TEST_COND_RET_ON_FAIL (2 == retry_counter,
11204 : "Re-confirming a confirmed challenge cost an"
11205 : " attempt\n");
11206 : /* ... but still only for the challenge's own body hash. */
11207 1 : TEST_COND_RET_ON_FAIL (
11208 : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
11209 : TALER_MERCHANTDB_do_solve_mfa_challenge (pg,
11210 : challenge_id,
11211 : &other_h_body,
11212 : "1234-5678",
11213 : &solved,
11214 : &retry_counter),
11215 : "Confirmed challenge was addressable with a different body hash\n");
11216 1 : TEST_COND_RET_ON_FAIL (! solved,
11217 : "Confirmed challenge solved with a different body"
11218 : " hash\n");
11219 1 : return 0;
11220 : }
11221 :
11222 :
11223 : /**
11224 : * Test that COMMIT on a transaction PostgreSQL has already aborted is not
11225 : * reported as a success -- and that a clean COMMIT still returns exactly
11226 : * GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, which is what every caller that
11227 : * compares the result against 0 relies on.
11228 : *
11229 : * @return 0 on success
11230 : */
11231 : static int
11232 1 : test_commit_on_aborted_transaction (void)
11233 : {
11234 1 : struct GNUNET_PQ_ExecuteStatement mk[] = {
11235 1 : GNUNET_PQ_make_execute ("CREATE TABLE test_commit_rollback (x INT)"),
11236 : GNUNET_PQ_EXECUTE_STATEMENT_END
11237 : };
11238 1 : struct GNUNET_PQ_ExecuteStatement bad[] = {
11239 1 : GNUNET_PQ_make_execute ("SELECT 1/0"),
11240 : GNUNET_PQ_EXECUTE_STATEMENT_END
11241 : };
11242 1 : struct GNUNET_PQ_ExecuteStatement chk[] = {
11243 1 : GNUNET_PQ_make_execute (
11244 : "DO $$ BEGIN"
11245 : " IF EXISTS (SELECT FROM pg_class"
11246 : " WHERE relname='test_commit_rollback')"
11247 : " THEN RAISE EXCEPTION 'table survived a rollback';"
11248 : " END IF; END $$"),
11249 : GNUNET_PQ_EXECUTE_STATEMENT_END
11250 : };
11251 :
11252 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
11253 : TALER_MERCHANTDB_start (pg,
11254 : "test-commit-aborted"),
11255 : "Failed to start transaction\n");
11256 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
11257 : GNUNET_PQ_exec_statements (pg->conn,
11258 : mk),
11259 : "Failed to create the scratch table\n");
11260 : /* provoke an error; from here on the transaction is doomed */
11261 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK !=
11262 : GNUNET_PQ_exec_statements (pg->conn,
11263 : bad),
11264 : "Division by zero unexpectedly succeeded\n");
11265 : /* before the fix this returned GNUNET_DB_STATUS_SUCCESS_NO_RESULTS (0) */
11266 1 : TEST_COND_RET_ON_FAIL (0 >
11267 : TALER_MERCHANTDB_commit (pg),
11268 : "COMMIT of an aborted transaction reported success\n");
11269 : /* ...and the table is indeed gone, so 'success' would have been a lie */
11270 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
11271 : GNUNET_PQ_exec_statements (pg->conn,
11272 : chk),
11273 : "Scratch table survived the rollback\n");
11274 1 : TEST_COND_RET_ON_FAIL (GNUNET_OK ==
11275 : TALER_MERCHANTDB_start (pg,
11276 : "test-commit-clean"),
11277 : "Failed to start transaction\n");
11278 1 : TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
11279 : TALER_MERCHANTDB_commit (pg),
11280 : "COMMIT changed its return value on success\n");
11281 1 : return 0;
11282 : }
11283 :
11284 :
11285 : /**
11286 : * Function that runs all tests.
11287 : *
11288 : * @return 0 on success, 1 otherwise.
11289 : */
11290 : static int
11291 1 : run_tests (void)
11292 : {
11293 1 : TEST_RET_ON_FAIL (test_instances ());
11294 1 : TEST_RET_ON_FAIL (test_products ());
11295 1 : TEST_RET_ON_FAIL (test_inventory_locks ());
11296 1 : TEST_RET_ON_FAIL (test_tokens ());
11297 1 : TEST_RET_ON_FAIL (test_orders ());
11298 1 : TEST_RET_ON_FAIL (test_deposits ());
11299 : /* This test relies on the global pending-webhook identity sequence not
11300 : having been advanced by an earlier test. */
11301 1 : TEST_RET_ON_FAIL (test_pending_webhooks ());
11302 1 : TEST_RET_ON_FAIL (test_transfers ());
11303 1 : TEST_RET_ON_FAIL (test_refunds ());
11304 1 : TEST_RET_ON_FAIL (test_lookup_orders_all_filters ());
11305 1 : TEST_RET_ON_FAIL (test_kyc ());
11306 1 : TEST_RET_ON_FAIL (test_templates ());
11307 1 : TEST_RET_ON_FAIL (test_webhooks ());
11308 1 : TEST_RET_ON_FAIL (test_inventory_deleted_webhook ());
11309 1 : TEST_RET_ON_FAIL (test_statistics ());
11310 1 : TEST_RET_ON_FAIL (test_mfa_challenges ());
11311 1 : TEST_RET_ON_FAIL (test_commit_on_aborted_transaction ());
11312 1 : return 0;
11313 : }
11314 :
11315 :
11316 : /**
11317 : * Main function that will be run by the scheduler.
11318 : *
11319 : * @param cls closure with config
11320 : */
11321 : static void
11322 1 : run (void *cls)
11323 : {
11324 1 : struct GNUNET_CONFIGURATION_Handle *cfg = cls;
11325 :
11326 1 : test_cfg = cfg;
11327 1 : if (NULL == (pg = TALER_MERCHANTDB_connect (cfg)))
11328 : {
11329 0 : result = 77;
11330 0 : return;
11331 : }
11332 : /* Drop the tables to cleanup anything that might cause issues */
11333 1 : (void) TALER_MERCHANTDB_drop_tables (pg);
11334 1 : if (GNUNET_OK !=
11335 1 : TALER_MERCHANTDB_create_tables (pg))
11336 : {
11337 0 : result = 77;
11338 0 : return;
11339 : }
11340 : /* Run the preflight */
11341 1 : TALER_MERCHANTDB_preflight (pg);
11342 :
11343 1 : result = run_tests ();
11344 : /* if (0 == result) result = run_test_templates (); */
11345 1 : if (0 == result)
11346 : {
11347 1 : if (GNUNET_OK !=
11348 1 : TALER_MERCHANTDB_drop_tables (pg))
11349 : {
11350 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
11351 : "Dropping tables failed\n");
11352 0 : result = 1;
11353 : }
11354 : }
11355 1 : TALER_MERCHANTDB_disconnect (pg);
11356 1 : pg = NULL;
11357 : }
11358 :
11359 :
11360 : /**
11361 : * Entry point for the tests.
11362 : */
11363 : int
11364 1 : main (int argc,
11365 : char *const argv[])
11366 : {
11367 : struct GNUNET_CONFIGURATION_Handle *cfg;
11368 :
11369 1 : GNUNET_log_setup (argv[0],
11370 : "DEBUG",
11371 : NULL);
11372 1 : cfg = GNUNET_CONFIGURATION_create (TALER_MERCHANT_project_data ());
11373 1 : if (GNUNET_OK !=
11374 1 : GNUNET_CONFIGURATION_parse (cfg,
11375 : "test-merchantdb-postgres.conf"))
11376 : {
11377 0 : GNUNET_break (0);
11378 0 : return 2;
11379 : }
11380 1 : GNUNET_SCHEDULER_run (&run,
11381 : cfg);
11382 1 : GNUNET_CONFIGURATION_destroy (cfg);
11383 1 : return result;
11384 : }
11385 :
11386 :
11387 : /* end of test_merchantdb.c */
|