donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

commit 037a791121a81a78c45b1be96ba3599a5e1103f8
parent ed9e169024d696e4a9c96ec800359ec47232f66f
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri,  7 Aug 2026 00:35:41 +0200

make get_charity follow iterate_charities in receipts_to_date handling

Diffstat:
Msrc/donaudb/get_charity.c | 22++++++++++++++++++----
Msrc/donaudb/test_donaudb.c | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 160 insertions(+), 7 deletions(-)

diff --git a/src/donaudb/get_charity.c b/src/donaudb/get_charity.c @@ -51,6 +51,7 @@ DONAUDB_get_charity ( &meta->current_year), GNUNET_PQ_result_spec_end }; + enum GNUNET_DB_QueryStatus qs; PREPARE (ctx, "get_charity", @@ -63,8 +64,21 @@ DONAUDB_get_charity ( " ,current_year" " FROM charities" " WHERE charity_id=$1;"); - return GNUNET_PQ_eval_prepared_singleton_select (ctx->conn, - "get_charity", - params, - rs); + qs = GNUNET_PQ_eval_prepared_singleton_select (ctx->conn, + "get_charity", + params, + rs); + if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && + (meta->current_year != GNUNET_TIME_get_current_year ()) ) + { + /* receipts_to_date is only reset by the first issue request of a new + year, so the stored value belongs to `current_year'. Report zero + for any other year, exactly as DONAUDB_iterate_charities() does -- + otherwise GET /charity/$ID and GET /charities disagree, and PATCH + refuses to lower max_per_year below *last* year's total. */ + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (meta->max_per_year.currency, + &meta->receipts_to_date)); + } + return qs; } diff --git a/src/donaudb/test_donaudb.c b/src/donaudb/test_donaudb.c @@ -356,6 +356,63 @@ iterate_active_signkeys_cb ( /** + * Closure for #check_charity_cb(). + */ +struct CharityCheck +{ + /** + * Charity we are interested in. + */ + uint64_t charity_id; + + /** + * Value #DONAUDB_iterate_charities() reported for it. + */ + struct TALER_Amount receipts_to_date; + + /** + * Set to true if the charity was seen. + */ + bool found; +}; + + +/** + * Capture what #DONAUDB_iterate_charities() reports for one charity. + * + * @param cls a `struct CharityCheck *` + * @param charity_id charity of this row + * @param charity_pub public key of the charity + * @param charity_name name of the charity + * @param max_per_year annual limit + * @param current_year year `receipts_to_date' belongs to + * @param receipts_to_date total booked so far + * @return #GNUNET_OK to continue + */ +static enum GNUNET_GenericReturnValue +check_charity_cb (void *cls, + uint64_t charity_id, + const struct DONAU_CharityPublicKeyP *charity_pub, + const char *charity_name, + const struct TALER_Amount *max_per_year, + uint32_t current_year, + const struct TALER_Amount *receipts_to_date) +{ + struct CharityCheck *cc = cls; + + (void) charity_pub; + (void) charity_name; + (void) max_per_year; + (void) current_year; + if (charity_id != cc->charity_id) + return GNUNET_OK; + cc->receipts_to_date = *receipts_to_date; + cc->found = true; + return GNUNET_OK; +} + + +/** * Closure for #collect_history_cb(). */ struct HistoryCheck @@ -776,10 +833,28 @@ run (void *cls) CURRENCY ":90", &under_limit)); FAILIF (! under_limit); - /* First request of the following year. */ + /* Pretend those 90 were booked last year: that is the state every + charity is in on the 1st of January, before its first request. */ + { + struct GNUNET_PQ_QueryParam uparams[] = { + GNUNET_PQ_query_param_uint64 (&rollover_charity_id), + GNUNET_PQ_query_param_end + }; + + FAILIF (GNUNET_OK != + GNUNET_PQ_prepare_anon (ctx->conn, + "UPDATE charities" + " SET current_year=current_year-1" + " WHERE charity_id=$1;")); + FAILIF (0 >= + GNUNET_PQ_eval_prepared_non_select (ctx->conn, + "", + uparams)); + } + /* First request of the new year. */ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != issue_receipt (rollover_charity_id, - this_year + 1, + this_year, CURRENCY ":5", &under_limit)); FAILIF (! under_limit); @@ -788,7 +863,7 @@ run (void *cls) 0, sizeof (hc)); hc.charity_id = (unsigned long long) rollover_charity_id; - hc.donation_year = this_year; + hc.donation_year = this_year - 1; GNUNET_assert (GNUNET_OK == TALER_string_to_amount (CURRENCY ":90", &hc.expected)); @@ -1049,6 +1124,70 @@ run (void *cls) params)); } + /* D-12: between the 1st of January and a charity's first request of the + year, `receipts_to_date' still holds last year's total. + DONAUDB_iterate_charities() compensates for that; DONAUDB_get_charity() + must do the same, otherwise GET /charity/$ID and GET /charities + disagree and PATCH refuses to lower max_per_year below *last* year's + total. */ + { + uint64_t stale_charity_id; + struct DONAUDB_CharityMetaData stale_meta; + struct CharityCheck cc; + struct TALER_Amount zero; + bool under_limit = false; + + FAILIF (GNUNET_OK != + make_charity (CURRENCY ":1000", + &stale_charity_id)); + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + issue_receipt (stale_charity_id, + GNUNET_TIME_get_current_year (), + CURRENCY ":90", + &under_limit)); + FAILIF (! under_limit); + { + struct GNUNET_PQ_QueryParam uparams[] = { + GNUNET_PQ_query_param_uint64 (&stale_charity_id), + GNUNET_PQ_query_param_end + }; + + FAILIF (GNUNET_OK != + GNUNET_PQ_prepare_anon (ctx->conn, + "UPDATE charities" + " SET current_year=current_year-1" + " WHERE charity_id=$1;")); + FAILIF (0 >= + GNUNET_PQ_eval_prepared_non_select (ctx->conn, + "", + uparams)); + } + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (CURRENCY, + &zero)); + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + DONAUDB_get_charity (ctx, + stale_charity_id, + &stale_meta)); + FAILIF (0 != TALER_amount_cmp (&stale_meta.receipts_to_date, + &zero)); + GNUNET_free (stale_meta.charity_name); + GNUNET_free (stale_meta.charity_url); + + /* ... and the two views agree. */ + memset (&cc, + 0, + sizeof (cc)); + cc.charity_id = stale_charity_id; + FAILIF (0 > + DONAUDB_iterate_charities (ctx, + &check_charity_cb, + &cc)); + FAILIF (! cc.found); + FAILIF (0 != TALER_amount_cmp (&cc.receipts_to_date, + &zero)); + } + result = 0; drop: