Coverage for tests/test_gate_unreleased_snap_pages.py: 100%

45 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-25 22:09 +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 with self.client.session_transaction() as session: 

61 session["publisher"][ 

62 "identity_url" 

63 ] = "https://login.ubuntu.com/test-user" 

64 session.modified = True 

65 

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

67 

68 self.assertEqual(202, response.status_code) 

69 mock_dashboard.snap_release_history.assert_not_called() 

70 

71 @patch("webapp.decorators._dashboard") 

72 def test_dashboard_error_does_not_block_request(self, mock_dashboard): 

73 mock_dashboard.snap_release_history.side_effect = Exception("boom") 

74 

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

76 

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

78 self.assertNotEqual(403, response.status_code) 

79 

80 @patch("webapp.decorators._dashboard") 

81 def test_unauthenticated_request_is_not_gated(self, mock_dashboard): 

82 unauth_client = self.app.test_client() 

83 mock_dashboard.snap_release_history.side_effect = AssertionError( 

84 "Unauthenticated requests should bypass the gate" 

85 ) 

86 

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

88 

89 # login_required redirects to /login; the gate should not be the one 

90 # short-circuiting the request. 

91 self.assertNotEqual(403, response.status_code) 

92 mock_dashboard.snap_release_history.assert_not_called()