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
« 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
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}"
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
24 response = self.client.get(self.endpoint_url)
26 # Assert response
27 self.assertEqual(response.status_code, 200)
28 self.assertIn(b"text/html", response.content_type.encode())
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)
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()
41 response = client.get(self.endpoint_url)
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])
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"
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 }
63 # Mock launchpad methods
64 mock_launchpad.is_snap_building.return_value = False
65 mock_launchpad.build_snap.return_value = "build-12345"
67 response = self.client.post(self.endpoint_url)
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")
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)
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 }
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"
95 response = self.client.post(self.endpoint_url)
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")
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)
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 = {}
116 response = self.client.post(self.endpoint_url)
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 )
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
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 }
138 # Mock launchpad methods
139 mock_launchpad.is_snap_building.return_value = False
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
149 response = self.client.post(self.endpoint_url)
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)
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()
167 response = client.post(self.endpoint_url)
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])
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/"
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()
186 response = client.post(self.endpoint_url)
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])