diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py index 57c9d81f4b5fa4397ca93b8e91cfad97e9b63447..6cc8f0230c6c392bd35c04c34300d1c64985c934 100644 --- a/src/_zkapauthorizer/controller.py +++ b/src/_zkapauthorizer/controller.py @@ -320,6 +320,24 @@ class UnpaidRedeemer(object): return fail(Unpaid(voucher)) +@implementer(IRedeemer) +@attr.s +class RecordingRedeemer(object): + """ + A ``CountingRedeemer`` delegates redemption logic to another object but + records all redemption attempts. + """ + original = attr.ib() + redemptions = attr.ib(default=attr.Factory(list)) + + def random_tokens_for_voucher(self, voucher, counter, count): + return dummy_random_tokens(voucher, counter, count) + + def redeemWithCounter(self, voucher, counter, random_tokens): + self.redemptions.append((voucher, counter, random_tokens)) + return self.original.redeemWithCounter(voucher, counter, random_tokens) + + def dummy_random_tokens(voucher, counter, count): v = urlsafe_b64decode(voucher.number.encode("ascii")) def dummy_random_token(n): diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index 48cfb2fbfa2c333fb51e5bcbee7cc3ba3d46b28f..df77bfec037d323c457161a1366e492f268ae1cb 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -109,6 +109,7 @@ from ..controller import ( UnpaidRedeemer, RistrettoRedeemer, IndexedRedeemer, + RecordingRedeemer, PaymentController, AlreadySpent, Unpaid, @@ -355,6 +356,37 @@ class PaymentControllerTests(TestCase): ), ) + + @given(tahoe_configs(), datetimes(), vouchers(), voucher_counters(), integers(min_value=0, max_value=100)) + def test_stop_redeeming_on_error(self, get_config, now, voucher, counter, extra_tokens): + """ + If an error is encountered on one of the redemption attempts performed by + ``IRedeemer.redeem``, the effort is suspended until the normal retry + logic activates. + """ + num_redemption_groups = counter + 1 + num_tokens = num_redemption_groups + extra_tokens + redeemer = RecordingRedeemer(UnpaidRedeemer()) + + store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store + controller = PaymentController( + store, + redeemer, + default_token_count=num_tokens, + num_redemption_groups=num_redemption_groups, + ) + self.assertThat( + controller.redeem(voucher), + succeeded(Always()), + ) + self.assertThat( + redeemer.redemptions, + AfterPreprocessing( + len, + Equals(1), + ), + ) + @given(tahoe_configs(), dummy_ristretto_keys(), datetimes(), vouchers()) def test_redeemed_after_redeeming(self, get_config, public_key, now, voucher): """