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

1from unittest.mock import patch 

2 

3from tests.endpoints.endpoint_testing import TestEndpoints 

4 

5 

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 """ 

12 

13 snap_name = "test-snap" 

14 

15 def _expect_release_history(self, mock_dashboard, revisions): 

16 mock_dashboard.snap_release_history.return_value = { 

17 "revisions": revisions, 

18 } 

19 

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=[]) 

23 

24 response = self.client.post(f"/api/{self.snap_name}/listing") 

25 

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"]) 

30 

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 ) 

36 

37 response = self.client.post(f"/api/{self.snap_name}/listing") 

38 

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) 

42 

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 ) 

49 

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() 

54 

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=[]) 

60 

61 response = self.client.delete(f"/packages/{self.snap_name}") 

62 

63 self.assertNotEqual(403, response.status_code) 

64 mock_dashboard.snap_release_history.assert_not_called() 

65 

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") 

69 

70 response = self.client.post(f"/api/{self.snap_name}/listing") 

71 

72 # On dashboard failure the gate passes through; do not return 403. 

73 self.assertNotEqual(403, response.status_code) 

74 

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 ) 

81 

82 response = unauth_client.post(f"/api/{self.snap_name}/listing") 

83 

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()