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:
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: