From baa18596a85b534583e1e2e164cb7011e241ec73 Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Fri, 3 Jul 2020 12:55:33 -0400
Subject: [PATCH] Test controller behavior when the response is not JSON

---
 src/_zkapauthorizer/controller.py            |  9 ++++
 src/_zkapauthorizer/tests/test_controller.py | 46 ++++++++++++++++++++
 2 files changed, 55 insertions(+)

diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py
index 3cb4c1d..c2ace39 100644
--- a/src/_zkapauthorizer/controller.py
+++ b/src/_zkapauthorizer/controller.py
@@ -103,6 +103,15 @@ from .model import (
 
 RETRY_INTERVAL = timedelta(milliseconds=1)
 
+@attr.s
+class UnexpectedResponse(Exception):
+    """
+    The issuer responded in an unexpected and unhandled way.
+    """
+    code = attr.ib()
+    body = attr.ib()
+
+
 class AlreadySpent(Exception):
     """
     An attempt was made to redeem a voucher which has already been redeemed.
diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py
index 3e8d6fc..7647440 100644
--- a/src/_zkapauthorizer/tests/test_controller.py
+++ b/src/_zkapauthorizer/tests/test_controller.py
@@ -50,6 +50,7 @@ from testtools.matchers import (
     HasLength,
     AfterPreprocessing,
     MatchesStructure,
+    ContainsDict,
 )
 from testtools.twistedsupport import (
     succeeded,
@@ -86,6 +87,7 @@ from twisted.web.http_headers import (
 from twisted.web.http import (
     UNSUPPORTED_MEDIA_TYPE,
     BAD_REQUEST,
+    INTERNAL_SERVER_ERROR,
 )
 from treq.testing import (
     StubTreq,
@@ -111,6 +113,7 @@ from ..controller import (
     IndexedRedeemer,
     RecordingRedeemer,
     PaymentController,
+    UnexpectedResponse,
     AlreadySpent,
     Unpaid,
     token_count_for_group,
@@ -621,6 +624,39 @@ class RistrettoRedeemerTests(TestCase):
             ),
         )
 
+    @given(voucher_objects(), voucher_counters(), integers(min_value=0, max_value=100))
+    def test_non_json_response(self, voucher, counter, num_tokens):
+        """
+        If the issuer responds with something that isn't JSON then the response is
+        logged and the ``Deferred`` fires with a ``Failure`` wrapping
+        ``UnexpectedResponse``.
+        """
+        issuer = UnexpectedResponseRedemption()
+        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,
+                    Equals(
+                        UnexpectedResponse(
+                            INTERNAL_SERVER_ERROR,
+                            b"Sorry, this server does not behave well.",
+                        ),
+                    ),
+                ),
+            ),
+        )
+
     @given(voucher_objects(), voucher_counters(), integers(min_value=0, max_value=100))
     def test_redemption_denied_alreadyspent(self, voucher, counter, extra_tokens):
         """
@@ -827,6 +863,16 @@ def stub_agent():
     return _StubAgent()
 
 
+class UnexpectedResponseRedemption(Resource):
+    """
+    An ``UnexpectedResponseRedemption`` simulates the Ristretto redemption
+    server but always returns a non-JSON error response.
+    """
+    def render_POST(self, request):
+        request.setResponseCode(INTERNAL_SERVER_ERROR)
+        return b"Sorry, this server does not behave well."
+
+
 class AlreadySpentRedemption(Resource):
     """
     An ``AlreadySpentRedemption`` simulates the Ristretto redemption server
-- 
GitLab