Coverage for tests/endpoints/publisher/tests_builds.py: 100%

100 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2025-08-26 22:06 +0000

1from unittest.mock import patch 

2from requests.exceptions import HTTPError 

3from tests.endpoints.endpoint_testing import TestEndpoints 

4 

5 

6class TestGetSnapBuildPage(TestEndpoints): 

7 def setUp(self): 

8 super().setUp() 

9 self.snap_name = "test-snap" 

10 self.build_id = "12345" 

11 self.endpoint_url = f"/{self.snap_name}/builds/{self.build_id}" 

12 

13 @patch("webapp.endpoints.publisher.builds.dashboard") 

14 def test_get_snap_build_page_success(self, mock_dashboard): 

15 """Test successful rendering of snap build page""" 

16 # Mock snap info response 

17 mock_snap_info = { 

18 "snap_name": self.snap_name, 

19 "title": "Test Snap", 

20 "snap_id": "test-snap-id-123", 

21 } 

22 mock_dashboard.get_snap_info.return_value = mock_snap_info 

23 

24 response = self.client.get(self.endpoint_url) 

25 

26 # Assert response 

27 self.assertEqual(response.status_code, 200) 

28 self.assertIn(b"text/html", response.content_type.encode()) 

29 

30 # Verify dashboard method was called with correct session and snap name 

31 mock_dashboard.get_snap_info.assert_called_once() 

32 call_args = mock_dashboard.get_snap_info.call_args 

33 self.assertEqual(call_args[0][1], self.snap_name) 

34 

35 def test_get_snap_build_page_requires_login(self): 

36 """Test that the endpoint requires login""" 

37 # Create a new client without logging in 

38 app = self.app 

39 client = app.test_client() 

40 

41 response = client.get(self.endpoint_url) 

42 

43 # Should redirect to login or return unauthorized 

44 # The exact behavior depends on the login_required decorator 

45 self.assertIn(response.status_code, [302, 401, 403]) 

46 

47 

48class TestPostBuild(TestEndpoints): 

49 def setUp(self): 

50 super().setUp() 

51 self.snap_name = "test-snap" 

52 self.endpoint_url = f"/api/{self.snap_name}/builds/trigger-build" 

53 

54 @patch("webapp.endpoints.publisher.builds.launchpad") 

55 @patch("webapp.endpoints.publisher.builds.dashboard") 

56 def test_post_build_success(self, mock_dashboard, mock_launchpad): 

57 """Test successful build trigger""" 

58 # Mock account snaps to include our test snap 

59 mock_dashboard.get_account_snaps.return_value = { 

60 self.snap_name: {"snap_name": self.snap_name} 

61 } 

62 

63 # Mock launchpad methods 

64 mock_launchpad.is_snap_building.return_value = False 

65 mock_launchpad.build_snap.return_value = "build-12345" 

66 

67 response = self.client.post(self.endpoint_url) 

68 

69 # Assert response 

70 self.assertEqual(response.status_code, 200) 

71 response_data = response.get_json() 

72 self.assertTrue(response_data["success"]) 

73 self.assertEqual(response_data["build_id"], "build-12345") 

74 

75 # Verify method calls 

76 mock_dashboard.get_account_snaps.assert_called_once() 

77 mock_launchpad.is_snap_building.assert_called_once_with(self.snap_name) 

78 mock_launchpad.build_snap.assert_called_once_with(self.snap_name) 

79 

80 @patch("webapp.endpoints.publisher.builds.launchpad") 

81 @patch("webapp.endpoints.publisher.builds.dashboard") 

82 def test_post_build_cancels_existing_build( 

83 self, mock_dashboard, mock_launchpad 

84 ): 

85 """Test that existing builds are cancelled before starting new one""" 

86 # Mock account snaps to include our test snap 

87 mock_dashboard.get_account_snaps.return_value = { 

88 self.snap_name: {"snap_name": self.snap_name} 

89 } 

90 

91 # Mock launchpad methods - existing build is running 

92 mock_launchpad.is_snap_building.return_value = True 

93 mock_launchpad.build_snap.return_value = "build-12345" 

94 

95 response = self.client.post(self.endpoint_url) 

96 

97 # Assert response 

98 self.assertEqual(response.status_code, 200) 

99 response_data = response.get_json() 

100 self.assertTrue(response_data["success"]) 

101 self.assertEqual(response_data["build_id"], "build-12345") 

102 

103 # Verify existing build was cancelled 

104 mock_launchpad.is_snap_building.assert_called_once_with(self.snap_name) 

105 mock_launchpad.cancel_snap_builds.assert_called_once_with( 

106 self.snap_name 

107 ) 

108 mock_launchpad.build_snap.assert_called_once_with(self.snap_name) 

109 

110 @patch("webapp.endpoints.publisher.builds.dashboard") 

111 def test_post_build_forbidden_non_contributor(self, mock_dashboard): 

112 """Test that non-contributors cannot trigger builds""" 

113 # Mock account snaps to NOT include our test snap 

114 mock_dashboard.get_account_snaps.return_value = {} 

115 

116 response = self.client.post(self.endpoint_url) 

117 

118 # Assert response 

119 self.assertEqual(response.status_code, 200) 

120 response_data = response.get_json() 

121 self.assertFalse(response_data["success"]) 

122 self.assertEqual(response_data["error"]["type"], "FORBIDDEN") 

123 self.assertIn( 

124 "not allowed to request builds", response_data["error"]["message"] 

125 ) 

126 

127 @patch("webapp.endpoints.publisher.builds.launchpad") 

128 @patch("webapp.endpoints.publisher.builds.dashboard") 

129 def test_post_build_http_error(self, mock_dashboard, mock_launchpad): 

130 """Test handling of HTTP errors from Launchpad""" 

131 from unittest.mock import Mock 

132 

133 # Mock account snaps to include our test snap 

134 mock_dashboard.get_account_snaps.return_value = { 

135 self.snap_name: {"snap_name": self.snap_name} 

136 } 

137 

138 # Mock launchpad methods 

139 mock_launchpad.is_snap_building.return_value = False 

140 

141 # Create mock HTTP error 

142 mock_response = Mock() 

143 mock_response.text = "Launchpad error message" 

144 mock_response.status_code = 500 

145 http_error = HTTPError() 

146 http_error.response = mock_response 

147 mock_launchpad.build_snap.side_effect = http_error 

148 

149 response = self.client.post(self.endpoint_url) 

150 

151 # Assert response 

152 self.assertEqual(response.status_code, 200) 

153 response_data = response.get_json() 

154 self.assertFalse(response_data["success"]) 

155 self.assertIn( 

156 "error happened building", response_data["error"]["message"] 

157 ) 

158 self.assertEqual(response_data["details"], "Launchpad error message") 

159 self.assertEqual(response_data["status_code"], 500) 

160 

161 def test_post_build_requires_login(self): 

162 """Test that the endpoint requires login""" 

163 # Create a new client without logging in 

164 app = self.app 

165 client = app.test_client() 

166 

167 response = client.post(self.endpoint_url) 

168 

169 # Should redirect to login or return unauthorized 

170 # The exact behavior depends on the login_required decorator 

171 self.assertIn(response.status_code, [302, 401, 403]) 

172 

173 

174class TestPostDisconnectRepo(TestEndpoints): 

175 def setUp(self): 

176 super().setUp() 

177 self.snap_name = "test-snap" 

178 self.endpoint_url = f"/api/{self.snap_name}/builds/disconnect/" 

179 

180 def test_post_disconnect_repo_requires_login(self): 

181 """Test that the endpoint requires login""" 

182 # Create a new client without logging in 

183 app = self.app 

184 client = app.test_client() 

185 

186 response = client.post(self.endpoint_url) 

187 

188 # Should redirect to login or return unauthorized 

189 # The exact behavior depends on the login_required decorator 

190 self.assertIn(response.status_code, [302, 401, 403])