We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Better HTTP response code handling in Eligibility API
In the Eligibility API `Client`, we currently attempt to parse the response as if it were a JWT, regardless of response code:

From https://github.com/cal-itp/benefits/blob/dev/benefits/eligibility/api.py#L145

```python
try:
    r = requests.get(self.verifier.api_url, headers=self._auth_headers(token))
except requests.ConnectionError:
    raise ApiError("Connection to verification server failed")
except requests.Timeout:
    raise ApiError("Connection to verification server timed out")
except requests.TooManyRedirects:
    raise ApiError("Too many redirects to verification server")
except requests.HTTPError as e:
    raise ApiError(e)

return self._tokenize_response(r)
```

Since input errors on the form are returned as JWTs, the same as success payloads, this code worked fine for 200 and 400 responses. But if the API outright rejects the call with a 403, the above code attempts to parse _that_ response as a JWT, throwing an unhandled exception.

Let's guard the `return self._tokenize_response(r)` to ensure we are only trying to tokenize the expected 200 and 400 responses; other codes should raise an `ApiError`.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `benefits/eligibility/api.py`
Content:
```
1 """
2 The eligibility application: Eligibility Verification API implementation.
3 """
4 import datetime
5 import json
6 import logging
7 import uuid
8 
9 from jwcrypto import common as jwcrypto, jwe, jws, jwt
10 import requests
11 
12 from benefits.settings import ALLOWED_HOSTS
13 
14 
15 logger = logging.getLogger(__name__)
16 
17 
18 class ApiError(Exception):
19     """Error calling the Eligibility Verification API."""
20 
21     pass
22 
23 
24 class TokenError(Exception):
25     """Error with API request/response token."""
26 
27     pass
28 
29 
30 class RequestToken:
31     """Eligibility Verification API request token."""
32 
33     def __init__(self, agency, verifier, sub, name):
34         logger.info("Initialize new request token")
35 
36         # send the eligibility type names
37         types = list(map(lambda t: t.name, agency.types_to_verify()))
38 
39         # craft the main token payload
40         payload = dict(
41             jti=str(uuid.uuid4()),
42             iss=ALLOWED_HOSTS[0],
43             iat=int(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp()),
44             agency=agency.agency_id,
45             eligibility=types,
46             sub=sub,
47             name=name,
48         )
49 
50         logger.debug("Sign token payload with agency's private key")
51         header = {"typ": "JWS", "alg": agency.jws_signing_alg}
52         signed_token = jwt.JWT(header=header, claims=payload)
53         signed_token.make_signed_token(agency.private_jwk)
54         signed_payload = signed_token.serialize()
55 
56         logger.debug("Encrypt signed token payload with verifier's public key")
57         header = {"typ": "JWE", "alg": verifier.jwe_encryption_alg, "enc": verifier.jwe_cek_enc}
58         encrypted_token = jwt.JWT(header=header, claims=signed_payload)
59         encrypted_token.make_encrypted_token(verifier.public_jwk)
60 
61         logger.info("Signed and encrypted request token initialized")
62         self._jwe = encrypted_token
63 
64     def __repr__(self):
65         return str(self)
66 
67     def __str__(self):
68         return self._jwe.serialize()
69 
70 
71 class ResponseToken:
72     """Eligibility Verification API response token."""
73 
74     def __init__(self, response, agency, verifier):
75         logger.info("Read encrypted token from response")
76 
77         try:
78             encrypted_signed_token = response.text
79             if not encrypted_signed_token:
80                 raise ValueError()
81             # strip extra spaces and wrapping quote chars
82             encrypted_signed_token = encrypted_signed_token.strip("'\n\"")
83         except ValueError:
84             raise TokenError("Invalid response format")
85 
86         logger.debug("Decrypt response token using agency's private key")
87         allowed_algs = [verifier.jwe_encryption_alg, verifier.jwe_cek_enc]
88         decrypted_token = jwe.JWE(algs=allowed_algs)
89         try:
90             decrypted_token.deserialize(encrypted_signed_token, key=agency.private_jwk)
91         except jwe.InvalidJWEData:
92             raise TokenError("Invalid JWE token")
93         except jwe.InvalidJWEOperation:
94             raise TokenError("JWE token decryption failed")
95 
96         decrypted_payload = str(decrypted_token.payload, "utf-8")
97 
98         logger.debug("Verify decrypted response token's signature using verifier's public key")
99         signed_token = jws.JWS()
100         try:
101             signed_token.deserialize(decrypted_payload, key=verifier.public_jwk, alg=agency.jws_signing_alg)
102         except jws.InvalidJWSObject:
103             raise TokenError("Invalid JWS token")
104         except jws.InvalidJWSSignature:
105             raise TokenError("JWS token signature verification failed")
106 
107         logger.info("Response token decrypted and signature verified")
108 
109         payload = json.loads(str(signed_token.payload, "utf-8"))
110         self.eligibility = list(payload.get("eligibility", []))
111         self.error = payload.get("error", None)
112 
113 
114 class Client:
115     """Eligibility Verification API HTTP client."""
116 
117     def __init__(self, agency):
118         logger.debug(f"Initialize client for agency: {agency.short_name}")
119         self.agency = agency
120         self.verifier = agency.eligibility_verifier
121 
122     def _tokenize_request(self, sub, name):
123         """Create a request token."""
124         return RequestToken(self.agency, self.verifier, sub, name)
125 
126     def _tokenize_response(self, response):
127         """Parse a response token."""
128         return ResponseToken(response, self.agency, self.verifier)
129 
130     def _auth_headers(self, token):
131         """Create headers for the request with the token and verifier API keys"""
132         headers = dict(Authorization=f"Bearer {token}")
133         headers[self.verifier.api_auth_header] = self.verifier.api_auth_key
134         return headers
135 
136     def _request(self, sub, name):
137         """Make an API request for eligibility verification."""
138         logger.debug("Start new eligibility verification request")
139 
140         try:
141             token = self._tokenize_request(sub, name)
142         except jwcrypto.JWException:
143             raise TokenError("Failed to tokenize form values")
144 
145         try:
146             logger.debug(f"GET request to {self.verifier.api_url}")
147             r = requests.get(self.verifier.api_url, headers=self._auth_headers(token))
148         except requests.ConnectionError:
149             raise ApiError("Connection to verification server failed")
150         except requests.Timeout:
151             raise ApiError("Connection to verification server timed out")
152         except requests.TooManyRedirects:
153             raise ApiError("Too many redirects to verification server")
154         except requests.HTTPError as e:
155             raise ApiError(e)
156 
157         logger.debug("Process eligiblity verification response")
158         return self._tokenize_response(r)
159 
160     def verify(self, sub, name):
161         """Check eligibility for the subject and name."""
162         return self._request(sub, name)
163
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/benefits/eligibility/api.py b/benefits/eligibility/api.py
--- a/benefits/eligibility/api.py
+++ b/benefits/eligibility/api.py
@@ -154,8 +154,13 @@
         except requests.HTTPError as e:
             raise ApiError(e)
 
-        logger.debug("Process eligiblity verification response")
-        return self._tokenize_response(r)
+        expected_status_codes = {200, 400}
+        if r.status_code in expected_status_codes:
+            logger.debug("Process eligiblity verification response")
+            return self._tokenize_response(r)
+        else:
+            logger.warning(f"Unexpected eligibility verification response status code: {r.status_code}")
+            raise ApiError("Unexpected eligibility verification response")
 
     def verify(self, sub, name):
         """Check eligibility for the subject and name."""

{"golden_diff": "diff --git a/benefits/eligibility/api.py b/benefits/eligibility/api.py\n--- a/benefits/eligibility/api.py\n+++ b/benefits/eligibility/api.py\n@@ -154,8 +154,13 @@\n         except requests.HTTPError as e:\n             raise ApiError(e)\n \n-        logger.debug(\"Process eligiblity verification response\")\n-        return self._tokenize_response(r)\n+        expected_status_codes = {200, 400}\n+        if r.status_code in expected_status_codes:\n+            logger.debug(\"Process eligiblity verification response\")\n+            return self._tokenize_response(r)\n+        else:\n+            logger.warning(f\"Unexpected eligibility verification response status code: {r.status_code}\")\n+            raise ApiError(\"Unexpected eligibility verification response\")\n \n     def verify(self, sub, name):\n         \"\"\"Check eligibility for the subject and name.\"\"\"\n", "issue": "Better HTTP response code handling in Eligibility API\nIn the Eligibility API `Client`, we currently attempt to parse the response as if it were a JWT, regardless of response code:\r\n\r\nFrom https://github.com/cal-itp/benefits/blob/dev/benefits/eligibility/api.py#L145\r\n\r\n```python\r\ntry:\r\n    r = requests.get(self.verifier.api_url, headers=self._auth_headers(token))\r\nexcept requests.ConnectionError:\r\n    raise ApiError(\"Connection to verification server failed\")\r\nexcept requests.Timeout:\r\n    raise ApiError(\"Connection to verification server timed out\")\r\nexcept requests.TooManyRedirects:\r\n    raise ApiError(\"Too many redirects to verification server\")\r\nexcept requests.HTTPError as e:\r\n    raise ApiError(e)\r\n\r\nreturn self._tokenize_response(r)\r\n```\r\n\r\nSince input errors on the form are returned as JWTs, the same as success payloads, this code worked fine for 200 and 400 responses. But if the API outright rejects the call with a 403, the above code attempts to parse _that_ response as a JWT, throwing an unhandled exception.\r\n\r\nLet's guard the `return self._tokenize_response(r)` to ensure we are only trying to tokenize the expected 200 and 400 responses; other codes should raise an `ApiError`.\n", "before_files": [{"content": "\"\"\"\nThe eligibility application: Eligibility Verification API implementation.\n\"\"\"\nimport datetime\nimport json\nimport logging\nimport uuid\n\nfrom jwcrypto import common as jwcrypto, jwe, jws, jwt\nimport requests\n\nfrom benefits.settings import ALLOWED_HOSTS\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass ApiError(Exception):\n    \"\"\"Error calling the Eligibility Verification API.\"\"\"\n\n    pass\n\n\nclass TokenError(Exception):\n    \"\"\"Error with API request/response token.\"\"\"\n\n    pass\n\n\nclass RequestToken:\n    \"\"\"Eligibility Verification API request token.\"\"\"\n\n    def __init__(self, agency, verifier, sub, name):\n        logger.info(\"Initialize new request token\")\n\n        # send the eligibility type names\n        types = list(map(lambda t: t.name, agency.types_to_verify()))\n\n        # craft the main token payload\n        payload = dict(\n            jti=str(uuid.uuid4()),\n            iss=ALLOWED_HOSTS[0],\n            iat=int(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp()),\n            agency=agency.agency_id,\n            eligibility=types,\n            sub=sub,\n            name=name,\n        )\n\n        logger.debug(\"Sign token payload with agency's private key\")\n        header = {\"typ\": \"JWS\", \"alg\": agency.jws_signing_alg}\n        signed_token = jwt.JWT(header=header, claims=payload)\n        signed_token.make_signed_token(agency.private_jwk)\n        signed_payload = signed_token.serialize()\n\n        logger.debug(\"Encrypt signed token payload with verifier's public key\")\n        header = {\"typ\": \"JWE\", \"alg\": verifier.jwe_encryption_alg, \"enc\": verifier.jwe_cek_enc}\n        encrypted_token = jwt.JWT(header=header, claims=signed_payload)\n        encrypted_token.make_encrypted_token(verifier.public_jwk)\n\n        logger.info(\"Signed and encrypted request token initialized\")\n        self._jwe = encrypted_token\n\n    def __repr__(self):\n        return str(self)\n\n    def __str__(self):\n        return self._jwe.serialize()\n\n\nclass ResponseToken:\n    \"\"\"Eligibility Verification API response token.\"\"\"\n\n    def __init__(self, response, agency, verifier):\n        logger.info(\"Read encrypted token from response\")\n\n        try:\n            encrypted_signed_token = response.text\n            if not encrypted_signed_token:\n                raise ValueError()\n            # strip extra spaces and wrapping quote chars\n            encrypted_signed_token = encrypted_signed_token.strip(\"'\\n\\\"\")\n        except ValueError:\n            raise TokenError(\"Invalid response format\")\n\n        logger.debug(\"Decrypt response token using agency's private key\")\n        allowed_algs = [verifier.jwe_encryption_alg, verifier.jwe_cek_enc]\n        decrypted_token = jwe.JWE(algs=allowed_algs)\n        try:\n            decrypted_token.deserialize(encrypted_signed_token, key=agency.private_jwk)\n        except jwe.InvalidJWEData:\n            raise TokenError(\"Invalid JWE token\")\n        except jwe.InvalidJWEOperation:\n            raise TokenError(\"JWE token decryption failed\")\n\n        decrypted_payload = str(decrypted_token.payload, \"utf-8\")\n\n        logger.debug(\"Verify decrypted response token's signature using verifier's public key\")\n        signed_token = jws.JWS()\n        try:\n            signed_token.deserialize(decrypted_payload, key=verifier.public_jwk, alg=agency.jws_signing_alg)\n        except jws.InvalidJWSObject:\n            raise TokenError(\"Invalid JWS token\")\n        except jws.InvalidJWSSignature:\n            raise TokenError(\"JWS token signature verification failed\")\n\n        logger.info(\"Response token decrypted and signature verified\")\n\n        payload = json.loads(str(signed_token.payload, \"utf-8\"))\n        self.eligibility = list(payload.get(\"eligibility\", []))\n        self.error = payload.get(\"error\", None)\n\n\nclass Client:\n    \"\"\"Eligibility Verification API HTTP client.\"\"\"\n\n    def __init__(self, agency):\n        logger.debug(f\"Initialize client for agency: {agency.short_name}\")\n        self.agency = agency\n        self.verifier = agency.eligibility_verifier\n\n    def _tokenize_request(self, sub, name):\n        \"\"\"Create a request token.\"\"\"\n        return RequestToken(self.agency, self.verifier, sub, name)\n\n    def _tokenize_response(self, response):\n        \"\"\"Parse a response token.\"\"\"\n        return ResponseToken(response, self.agency, self.verifier)\n\n    def _auth_headers(self, token):\n        \"\"\"Create headers for the request with the token and verifier API keys\"\"\"\n        headers = dict(Authorization=f\"Bearer {token}\")\n        headers[self.verifier.api_auth_header] = self.verifier.api_auth_key\n        return headers\n\n    def _request(self, sub, name):\n        \"\"\"Make an API request for eligibility verification.\"\"\"\n        logger.debug(\"Start new eligibility verification request\")\n\n        try:\n            token = self._tokenize_request(sub, name)\n        except jwcrypto.JWException:\n            raise TokenError(\"Failed to tokenize form values\")\n\n        try:\n            logger.debug(f\"GET request to {self.verifier.api_url}\")\n            r = requests.get(self.verifier.api_url, headers=self._auth_headers(token))\n        except requests.ConnectionError:\n            raise ApiError(\"Connection to verification server failed\")\n        except requests.Timeout:\n            raise ApiError(\"Connection to verification server timed out\")\n        except requests.TooManyRedirects:\n            raise ApiError(\"Too many redirects to verification server\")\n        except requests.HTTPError as e:\n            raise ApiError(e)\n\n        logger.debug(\"Process eligiblity verification response\")\n        return self._tokenize_response(r)\n\n    def verify(self, sub, name):\n        \"\"\"Check eligibility for the subject and name.\"\"\"\n        return self._request(sub, name)\n", "path": "benefits/eligibility/api.py"}], "after_files": [{"content": "\"\"\"\nThe eligibility application: Eligibility Verification API implementation.\n\"\"\"\nimport datetime\nimport json\nimport logging\nimport uuid\n\nfrom jwcrypto import common as jwcrypto, jwe, jws, jwt\nimport requests\n\nfrom benefits.settings import ALLOWED_HOSTS\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass ApiError(Exception):\n    \"\"\"Error calling the Eligibility Verification API.\"\"\"\n\n    pass\n\n\nclass TokenError(Exception):\n    \"\"\"Error with API request/response token.\"\"\"\n\n    pass\n\n\nclass RequestToken:\n    \"\"\"Eligibility Verification API request token.\"\"\"\n\n    def __init__(self, agency, verifier, sub, name):\n        logger.info(\"Initialize new request token\")\n\n        # send the eligibility type names\n        types = list(map(lambda t: t.name, agency.types_to_verify()))\n\n        # craft the main token payload\n        payload = dict(\n            jti=str(uuid.uuid4()),\n            iss=ALLOWED_HOSTS[0],\n            iat=int(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).timestamp()),\n            agency=agency.agency_id,\n            eligibility=types,\n            sub=sub,\n            name=name,\n        )\n\n        logger.debug(\"Sign token payload with agency's private key\")\n        header = {\"typ\": \"JWS\", \"alg\": agency.jws_signing_alg}\n        signed_token = jwt.JWT(header=header, claims=payload)\n        signed_token.make_signed_token(agency.private_jwk)\n        signed_payload = signed_token.serialize()\n\n        logger.debug(\"Encrypt signed token payload with verifier's public key\")\n        header = {\"typ\": \"JWE\", \"alg\": verifier.jwe_encryption_alg, \"enc\": verifier.jwe_cek_enc}\n        encrypted_token = jwt.JWT(header=header, claims=signed_payload)\n        encrypted_token.make_encrypted_token(verifier.public_jwk)\n\n        logger.info(\"Signed and encrypted request token initialized\")\n        self._jwe = encrypted_token\n\n    def __repr__(self):\n        return str(self)\n\n    def __str__(self):\n        return self._jwe.serialize()\n\n\nclass ResponseToken:\n    \"\"\"Eligibility Verification API response token.\"\"\"\n\n    def __init__(self, response, agency, verifier):\n        logger.info(\"Read encrypted token from response\")\n\n        try:\n            encrypted_signed_token = response.text\n            if not encrypted_signed_token:\n                raise ValueError()\n            # strip extra spaces and wrapping quote chars\n            encrypted_signed_token = encrypted_signed_token.strip(\"'\\n\\\"\")\n        except ValueError:\n            raise TokenError(\"Invalid response format\")\n\n        logger.debug(\"Decrypt response token using agency's private key\")\n        allowed_algs = [verifier.jwe_encryption_alg, verifier.jwe_cek_enc]\n        decrypted_token = jwe.JWE(algs=allowed_algs)\n        try:\n            decrypted_token.deserialize(encrypted_signed_token, key=agency.private_jwk)\n        except jwe.InvalidJWEData:\n            raise TokenError(\"Invalid JWE token\")\n        except jwe.InvalidJWEOperation:\n            raise TokenError(\"JWE token decryption failed\")\n\n        decrypted_payload = str(decrypted_token.payload, \"utf-8\")\n\n        logger.debug(\"Verify decrypted response token's signature using verifier's public key\")\n        signed_token = jws.JWS()\n        try:\n            signed_token.deserialize(decrypted_payload, key=verifier.public_jwk, alg=agency.jws_signing_alg)\n        except jws.InvalidJWSObject:\n            raise TokenError(\"Invalid JWS token\")\n        except jws.InvalidJWSSignature:\n            raise TokenError(\"JWS token signature verification failed\")\n\n        logger.info(\"Response token decrypted and signature verified\")\n\n        payload = json.loads(str(signed_token.payload, \"utf-8\"))\n        self.eligibility = list(payload.get(\"eligibility\", []))\n        self.error = payload.get(\"error\", None)\n\n\nclass Client:\n    \"\"\"Eligibility Verification API HTTP client.\"\"\"\n\n    def __init__(self, agency):\n        logger.debug(f\"Initialize client for agency: {agency.short_name}\")\n        self.agency = agency\n        self.verifier = agency.eligibility_verifier\n\n    def _tokenize_request(self, sub, name):\n        \"\"\"Create a request token.\"\"\"\n        return RequestToken(self.agency, self.verifier, sub, name)\n\n    def _tokenize_response(self, response):\n        \"\"\"Parse a response token.\"\"\"\n        return ResponseToken(response, self.agency, self.verifier)\n\n    def _auth_headers(self, token):\n        \"\"\"Create headers for the request with the token and verifier API keys\"\"\"\n        headers = dict(Authorization=f\"Bearer {token}\")\n        headers[self.verifier.api_auth_header] = self.verifier.api_auth_key\n        return headers\n\n    def _request(self, sub, name):\n        \"\"\"Make an API request for eligibility verification.\"\"\"\n        logger.debug(\"Start new eligibility verification request\")\n\n        try:\n            token = self._tokenize_request(sub, name)\n        except jwcrypto.JWException:\n            raise TokenError(\"Failed to tokenize form values\")\n\n        try:\n            logger.debug(f\"GET request to {self.verifier.api_url}\")\n            r = requests.get(self.verifier.api_url, headers=self._auth_headers(token))\n        except requests.ConnectionError:\n            raise ApiError(\"Connection to verification server failed\")\n        except requests.Timeout:\n            raise ApiError(\"Connection to verification server timed out\")\n        except requests.TooManyRedirects:\n            raise ApiError(\"Too many redirects to verification server\")\n        except requests.HTTPError as e:\n            raise ApiError(e)\n\n        expected_status_codes = {200, 400}\n        if r.status_code in expected_status_codes:\n            logger.debug(\"Process eligiblity verification response\")\n            return self._tokenize_response(r)\n        else:\n            logger.warning(f\"Unexpected eligibility verification response status code: {r.status_code}\")\n            raise ApiError(\"Unexpected eligibility verification response\")\n\n    def verify(self, sub, name):\n        \"\"\"Check eligibility for the subject and name.\"\"\"\n        return self._request(sub, name)\n", "path": "benefits/eligibility/api.py"}]}