Coverage for tests/publisher/endpoint_testing.py: 92%
153 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-08 13:26 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-08 13:26 +0000
1from unittest.mock import patch
3import requests
5import responses
6from flask_testing import TestCase
7from webapp.app import create_app
8from webapp.authentication import get_authorization_header
10# Make sure tests fail on stray responses.
11responses.mock.assert_all_requests_are_fired = True
14class BaseTestCases:
15 """
16 This class has a set of test classes that should be inherited by endpoint
17 that have authentication.
19 It is also used to avoid unittest to run this tests file.
20 """
22 class BaseAppTesting(TestCase):
23 def setUp(self, snap_name, api_url, endpoint_url):
24 self.snap_name = snap_name
25 self.api_url = api_url
26 self.endpoint_url = endpoint_url
28 def tearDown(self):
29 responses.reset()
31 def create_app(self):
32 app = create_app(testing=True)
33 app.secret_key = "secret_key"
34 app.config["WTF_CSRF_METHODS"] = []
36 return app
38 def _get_location(self):
39 return "{}".format(self.endpoint_url)
41 def _log_in(self, client):
42 """Emulates test client login in the store.
44 Fill current session with `openid` and `macaroon_exchanged`.
46 Return the expected `Authorization` header for further verification
47 in API requests.
48 """
49 exchanged_macaroon = "test-exchanged-macaroon"
51 with client.session_transaction() as s:
52 s["publisher"] = {
53 "image": None,
54 "nickname": "Toto",
55 "fullname": "El Toto",
56 "email": "testing@testing.com",
57 "stores": [],
58 }
59 s["macaroon_exchanged"] = exchanged_macaroon
61 return get_authorization_header(exchanged_macaroon)
63 def check_call_by_api_url(self, calls):
64 found = False
65 for called in calls:
66 if self.api_url == called.request.url:
67 found = True
68 self.assertEqual(
69 self.authorization,
70 called.request.headers.get("Authorization"),
71 )
73 assert found
75 class EndpointLoggedOut(BaseAppTesting):
76 def setUp(self, snap_name, endpoint_url, method_endpoint="GET"):
77 self.method_endpoint = method_endpoint
78 super().setUp(snap_name, None, endpoint_url)
80 def test_access_not_logged_in(self):
81 if self.method_endpoint == "GET":
82 response = self.client.get(self.endpoint_url)
83 else:
84 response = self.client.post(self.endpoint_url, data={})
86 self.assertEqual(302, response.status_code)
87 self.assertEqual(
88 "/login?next={}".format(self.endpoint_url),
89 response.location,
90 )
92 class EndpointLoggedIn(BaseAppTesting):
93 def setUp(
94 self,
95 snap_name,
96 endpoint_url,
97 api_url,
98 method_endpoint="GET",
99 method_api="GET",
100 data=None,
101 json=None,
102 ):
103 super().setUp(
104 snap_name=snap_name, api_url=api_url, endpoint_url=endpoint_url
105 )
107 # Stub the blueprint-level "has releases" gate so each test does
108 # not need to mock the extra dashboard call. Tests that exercise
109 # the gate directly can override this in their own setUp.
110 self._release_history_patcher = patch(
111 "webapp.decorators._dashboard.snap_release_history",
112 return_value={"revisions": [{"revision": 1}]},
113 )
114 self._release_history_patcher.start()
115 self.addCleanup(self._release_history_patcher.stop)
117 self.method_endpoint = method_endpoint
118 self.method_api = method_api
119 self.data = data
120 self.json = json
121 self.authorization = self._log_in(self.client)
123 @responses.activate
124 def test_timeout(self):
125 responses.add(
126 responses.Response(
127 method=self.method_api,
128 url=self.api_url,
129 body=requests.exceptions.Timeout(),
130 status=504,
131 )
132 )
134 if self.method_endpoint == "GET":
135 response = self.client.get(self.endpoint_url)
136 else:
137 if self.data:
138 response = self.client.post(
139 self.endpoint_url, data=self.data
140 )
141 else:
142 response = self.client.post(
143 self.endpoint_url, json=self.json
144 )
146 self.check_call_by_api_url(responses.calls)
148 assert response.status_code == 504
150 @responses.activate
151 def test_connection_error(self):
152 responses.add(
153 responses.Response(
154 method=self.method_api,
155 url=self.api_url,
156 body=requests.exceptions.ConnectionError(),
157 status=500,
158 )
159 )
161 if self.method_endpoint == "GET":
162 response = self.client.get(self.endpoint_url)
163 else:
164 if self.data:
165 response = self.client.post(
166 self.endpoint_url, data=self.data
167 )
168 else:
169 response = self.client.post(
170 self.endpoint_url, json=self.json
171 )
173 self.check_call_by_api_url(responses.calls)
175 assert response.status_code == 502
177 @responses.activate
178 def test_broken_json(self):
179 # To test this I return no json from the server, this makes the
180 # call to the function response.json() raise a ValueError exception
181 responses.add(
182 responses.Response(
183 method=self.method_api, url=self.api_url, status=500
184 )
185 )
187 if self.method_endpoint == "GET":
188 response = self.client.get(self.endpoint_url)
189 else:
190 if self.data:
191 response = self.client.post(
192 self.endpoint_url, data=self.data
193 )
194 else:
195 response = self.client.post(
196 self.endpoint_url, json=self.json
197 )
199 self.check_call_by_api_url(responses.calls)
201 assert response.status_code == 502
203 @responses.activate
204 def test_unknown_error(self):
205 responses.add(
206 responses.Response(
207 method=self.method_api,
208 url=self.api_url,
209 json={},
210 status=500,
211 )
212 )
214 if self.method_endpoint == "GET":
215 response = self.client.get(self.endpoint_url)
216 else:
217 if self.data:
218 response = self.client.post(
219 self.endpoint_url, data=self.data
220 )
221 else:
222 response = self.client.post(
223 self.endpoint_url, json=self.json
224 )
226 self.check_call_by_api_url(responses.calls)
228 assert response.status_code == 502
230 @responses.activate
231 def test_expired_macaroon(self):
232 responses.add(
233 responses.Response(
234 method=self.method_api,
235 url=self.api_url,
236 json={},
237 status=401,
238 headers={"WWW-Authenticate": "Macaroon needs_refresh=1"},
239 )
240 )
241 if self.method_endpoint == "GET":
242 response = self.client.get(self.endpoint_url)
243 else:
244 if self.data:
245 response = self.client.post(
246 self.endpoint_url, data=self.data
247 )
248 else:
249 response = self.client.post(
250 self.endpoint_url, json=self.json
251 )
253 assert response.status_code == 302
254 assert response.location == (f"/login?next={self.endpoint_url}")
256 class EndpointLoggedInErrorHandling(EndpointLoggedIn):
257 @responses.activate
258 def test_error_4xx(self):
259 payload = {"error_list": []}
260 responses.add(
261 responses.Response(
262 method=self.method_api,
263 url=self.api_url,
264 json=payload,
265 status=400,
266 )
267 )
269 if self.method_endpoint == "GET":
270 response = self.client.get(self.endpoint_url)
271 else:
272 if self.data:
273 response = self.client.post(
274 self.endpoint_url, data=self.data
275 )
276 else:
277 response = self.client.post(
278 self.endpoint_url, json=self.json
279 )
281 self.check_call_by_api_url(responses.calls)
283 assert response.status_code == 502
285 @responses.activate
286 def test_custom_error(self):
287 payload = {
288 "error_list": [
289 {"code": "error-code1"},
290 {"code": "error-code2"},
291 ]
292 }
293 responses.add(
294 responses.Response(
295 method=self.method_api,
296 url=self.api_url,
297 json=payload,
298 status=400,
299 )
300 )
302 if self.method_endpoint == "GET":
303 response = self.client.get(self.endpoint_url)
304 else:
305 if self.data:
306 response = self.client.post(
307 self.endpoint_url, data=self.data
308 )
309 else:
310 response = self.client.post(
311 self.endpoint_url, json=self.json
312 )
314 self.check_call_by_api_url(responses.calls)
316 assert response.status_code == 502
318 @responses.activate
319 def test_account_not_signed_agreement_logged_in(self):
320 payload = {
321 "error_list": [
322 {
323 "code": "user-not-ready",
324 "message": "has not signed agreement",
325 }
326 ]
327 }
328 responses.add(
329 responses.Response(
330 method=self.method_api,
331 url=self.api_url,
332 json=payload,
333 status=403,
334 )
335 )
337 if self.method_endpoint == "GET":
338 response = self.client.get(self.endpoint_url)
339 else:
340 if self.data:
341 response = self.client.post(
342 self.endpoint_url, data=self.data
343 )
344 else:
345 response = self.client.post(
346 self.endpoint_url, json=self.json
347 )
349 self.check_call_by_api_url(responses.calls)
351 self.assertEqual(302, response.status_code)
352 self.assertEqual("/account/agreement", response.location)
354 @responses.activate
355 def test_account_no_username_logged_in(self):
356 payload = {
357 "error_list": [
358 {
359 "code": "user-not-ready",
360 "message": "missing store username",
361 }
362 ]
363 }
364 responses.add(
365 responses.Response(
366 method=self.method_api,
367 url=self.api_url,
368 json=payload,
369 status=403,
370 )
371 )
373 if self.method_endpoint == "GET":
374 response = self.client.get(self.endpoint_url)
375 else:
376 if self.data:
377 response = self.client.post(
378 self.endpoint_url, data=self.data
379 )
380 else:
381 response = self.client.post(
382 self.endpoint_url, json=self.json
383 )
385 self.check_call_by_api_url(responses.calls)
387 self.assertEqual(302, response.status_code)
388 self.assertEqual("/account/username", response.location)