Coverage for tests/test_gate_unreleased_snap_pages.py: 100%
42 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 22:43 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-15 22:43 +0000
1from unittest.mock import patch
3from tests.endpoints.endpoint_testing import TestEndpoints
6class TestGateUnreleasedSnapPages(TestEndpoints):
7 """
8 The blueprint-level gate blocks state-changing requests to per-<snap_name>
9 routes when the snap has no published revisions. Read requests, skip-listed
10 endpoints, and dashboard errors must pass through.
11 """
13 snap_name = "test-snap"
15 def _expect_release_history(self, mock_dashboard, revisions):
16 mock_dashboard.snap_release_history.return_value = {
17 "revisions": revisions,
18 }
20 @patch("webapp.decorators._dashboard")
21 def test_post_blocked_for_snap_with_no_revisions(self, mock_dashboard):
22 self._expect_release_history(mock_dashboard, revisions=[])
24 response = self.client.post(f"/api/{self.snap_name}/listing")
26 self.assertEqual(403, response.status_code)
27 body = response.get_json()
28 self.assertFalse(body["success"])
29 self.assertEqual("no-releases", body["errors"][0]["code"])
31 @patch("webapp.decorators._dashboard")
32 def test_post_passes_through_for_snap_with_revisions(self, mock_dashboard):
33 self._expect_release_history(
34 mock_dashboard, revisions=[{"revision": 1}]
35 )
37 response = self.client.post(f"/api/{self.snap_name}/listing")
39 # Gate is out of the way; the downstream handler runs. We don't assert
40 # the exact downstream response, only that it isn't the gate's 403.
41 self.assertNotEqual(403, response.status_code)
43 @patch("webapp.decorators._dashboard")
44 def test_get_is_not_gated(self, mock_dashboard):
45 # snap_release_history should not even be consulted for a GET request.
46 mock_dashboard.snap_release_history.side_effect = AssertionError(
47 "GET should bypass the gate"
48 )
50 # We don't care about the eventual status — only that the gate did not
51 # short-circuit with 403 and did not call snap_release_history.
52 self.client.get(f"/api/{self.snap_name}/listing")
53 mock_dashboard.snap_release_history.assert_not_called()
55 @patch("webapp.decorators._dashboard")
56 def test_delete_package_endpoint_is_skip_listed(self, mock_dashboard):
57 # delete_package must remain reachable so users can unregister names
58 # that have never had a release.
59 self._expect_release_history(mock_dashboard, revisions=[])
61 response = self.client.delete(f"/packages/{self.snap_name}")
63 self.assertNotEqual(403, response.status_code)
64 mock_dashboard.snap_release_history.assert_not_called()
66 @patch("webapp.decorators._dashboard")
67 def test_dashboard_error_does_not_block_request(self, mock_dashboard):
68 mock_dashboard.snap_release_history.side_effect = Exception("boom")
70 response = self.client.post(f"/api/{self.snap_name}/listing")
72 # On dashboard failure the gate passes through; do not return 403.
73 self.assertNotEqual(403, response.status_code)
75 @patch("webapp.decorators._dashboard")
76 def test_unauthenticated_request_is_not_gated(self, mock_dashboard):
77 unauth_client = self.app.test_client()
78 mock_dashboard.snap_release_history.side_effect = AssertionError(
79 "Unauthenticated requests should bypass the gate"
80 )
82 response = unauth_client.post(f"/api/{self.snap_name}/listing")
84 # login_required redirects to /login; the gate should not be the one
85 # short-circuiting the request.
86 self.assertNotEqual(403, response.status_code)
87 mock_dashboard.snap_release_history.assert_not_called()