From 1b52464df578753739e02e3fa1a37f40b54309dd Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Mon, 8 Nov 2021 14:10:21 -0500
Subject: [PATCH] test unrecognized unsuccessful redemption reason case, and
 fix it

---
 src/_zkapauthorizer/controller.py            | 10 +++++++
 src/_zkapauthorizer/tests/test_controller.py | 29 ++++++++++++++++++++
 2 files changed, 39 insertions(+)

diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py
index 62d65c8..d86a8b8 100644
--- a/src/_zkapauthorizer/controller.py
+++ b/src/_zkapauthorizer/controller.py
@@ -78,6 +78,14 @@ class Unpaid(Exception):
     """
 
 
+class UnrecognizedFailureReason(Exception):
+    """
+    An attempt was made to redeem a voucher and the response contained an unknown reason.
+
+    The redemption attempt may be automatically retried at some point.
+    """
+
+
 @attr.s
 class RedemptionResult(object):
     """
@@ -522,6 +530,8 @@ class RistrettoRedeemer(object):
             elif reason == u"unpaid":
                 raise Unpaid(voucher)
 
+            raise UnrecognizedFailureReason()
+
         self._log.info(
             "Redeemed: {public_key} {proof} {count}",
             public_key=result[u"public-key"],
diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py
index a87548d..51aa113 100644
--- a/src/_zkapauthorizer/tests/test_controller.py
+++ b/src/_zkapauthorizer/tests/test_controller.py
@@ -58,6 +58,7 @@ from twisted.web.resource import ErrorPage, Resource
 from zope.interface import implementer
 
 from ..controller import (
+    UnrecognizedFailureReason,
     AlreadySpent,
     DoubleSpendRedeemer,
     ErrorRedeemer,
@@ -885,6 +886,34 @@ class RistrettoRedeemerTests(TestCase):
             ),
         )
 
+    @given(voucher_objects(), voucher_counters(), integers(min_value=0, max_value=100))
+    def test_redemption_unknown_response(self, voucher, counter, extra_tokens):
+        """
+        If the issuer returns a failure without a recognizable reason then
+        ``RistrettoRedeemer.redeemWithCounter`` returns a ``Deferred`` that
+        fails with ``UnrecognizedFailureReason``.
+        """
+        num_tokens = counter + extra_tokens
+        issuer = UnsuccessfulRedemption(u"mysterious")
+        treq = treq_for_loopback_ristretto(issuer)
+        redeemer = RistrettoRedeemer(treq, NOWHERE)
+        random_tokens = redeemer.random_tokens_for_voucher(voucher, counter, num_tokens)
+        d = redeemer.redeemWithCounter(
+            voucher,
+            counter,
+            random_tokens,
+        )
+        self.assertThat(
+            d,
+            failed(
+                AfterPreprocessing(
+                    lambda f: f.value,
+                    IsInstance(UnrecognizedFailureReason),
+                ),
+            ),
+        )
+
+
     @given(voucher_objects(), voucher_counters(), integers(min_value=0, max_value=100))
     def test_bad_ristretto_redemption(self, voucher, counter, extra_tokens):
         """
-- 
GitLab