|
1 | 1 | import typing |
2 | 2 |
|
| 3 | +import httpcore2 |
3 | 4 | import httpware |
4 | 5 | import httpx |
5 | 6 | import pydantic |
@@ -156,12 +157,105 @@ def test_a_failing_service_keeps_its_cache_and_does_not_stop_the_others(gitlab: |
156 | 157 | report: typing.Final = _collect(next_cache) |
157 | 158 |
|
158 | 159 | assert [(item.project, item.error) for item in report.services] == [ |
159 | | - ("team/broken", "GitLab returned 500 for /api/v4/projects/2/deployments."), |
160 | | - ("team/svc", "GitLab returned 502 for /api/v4/projects/1/deployments."), |
| 160 | + ("team/broken", "team/broken: GitLab returned 500 for deployments."), |
| 161 | + ("team/svc", "team/svc: GitLab returned 502 for deployments."), |
161 | 162 | ] |
162 | 163 | assert next_cache.current.pipelines["1"] == cache.current.pipelines["1"] |
163 | 164 |
|
164 | 165 |
|
| 166 | +def test_forbidden_deployments_fail_only_that_service_and_say_where_to_look(gitlab: respx.Router) -> None: |
| 167 | + gitlab["group"].respond(json=[SERVICE, project(2, "team/nodeploy")]) |
| 168 | + gitlab.get(f"{API}/projects/2/deployments").respond(403) |
| 169 | + settings: typing.Final = f"{ENDPOINT}/team/nodeploy/edit#js-shared-permissions" |
| 170 | + |
| 171 | + report: typing.Final = _collect() |
| 172 | + |
| 173 | + assert [item.project for item in report.services] == ["team/nodeploy", "team/svc"] |
| 174 | + assert report.services[0].error == ( |
| 175 | + "team/nodeploy: GitLab denied access to deployments (403). Check that:\n" |
| 176 | + f"- Environments are enabled: {settings} → Visibility, project features, permissions → Environments\n" |
| 177 | + f"- CI/CD is enabled: {settings} → Visibility, project features, permissions → CI/CD\n" |
| 178 | + f"- the token's user has a role that can read them: {ENDPOINT}/team/nodeploy/-/project_members" |
| 179 | + ) |
| 180 | + assert len(report.services[1].rows) == 5 |
| 181 | + |
| 182 | + |
| 183 | +@pytest.mark.httpx2(assert_all_called=False) |
| 184 | +@pytest.mark.parametrize( |
| 185 | + ("route", "resource", "feature", "enabled"), |
| 186 | + [ |
| 187 | + ("commits", "the repository", "Repository", "Repository is enabled"), |
| 188 | + ("merged_mrs", "merge requests", "Merge requests", "Merge requests are enabled"), |
| 189 | + ("push_pipelines", "pipelines", "CI/CD", "CI/CD is enabled"), |
| 190 | + ], |
| 191 | +) |
| 192 | +def test_forbidden_resource_lists_the_feature_that_guards_it( |
| 193 | + gitlab: respx.Router, route: str, resource: str, feature: str, enabled: str |
| 194 | +) -> None: |
| 195 | + gitlab[route].respond(403) |
| 196 | + settings: typing.Final = f"{ENDPOINT}/team/svc/edit#js-shared-permissions" |
| 197 | + |
| 198 | + error: typing.Final = _only_service(_collect()).error |
| 199 | + |
| 200 | + assert error == ( |
| 201 | + f"team/svc: GitLab denied access to {resource} (403). Check that:\n" |
| 202 | + f"- {enabled}: {settings} → Visibility, project features, permissions → {feature}\n" |
| 203 | + f"- the token's user has a role that can read them: {ENDPOINT}/team/svc/-/project_members" |
| 204 | + ) |
| 205 | + |
| 206 | + |
| 207 | +@pytest.mark.httpx2(assert_all_called=False) |
| 208 | +def test_network_failure_names_the_project(gitlab: respx.Router) -> None: |
| 209 | + gitlab["tags"].mock(side_effect=httpcore2.ConnectError("down")) |
| 210 | + |
| 211 | + assert _only_service(_collect()).error == "team/svc: GitLab request for the repository failed (NetworkError)." |
| 212 | + |
| 213 | + |
| 214 | +_LIB_FEATURES: typing.Final = ( |
| 215 | + f"{ENDPOINT}/team/lib/edit#js-shared-permissions → Visibility, project features, permissions" |
| 216 | +) |
| 217 | + |
| 218 | + |
| 219 | +@pytest.mark.parametrize( |
| 220 | + ("access_levels", "warning"), |
| 221 | + [ |
| 222 | + ( |
| 223 | + {"environments_access_level": "disabled"}, |
| 224 | + f"Environments are disabled, so it has no deployments. Enable them at {_LIB_FEATURES} → Environments.", |
| 225 | + ), |
| 226 | + ( |
| 227 | + {"builds_access_level": "disabled"}, |
| 228 | + f"CI/CD is disabled, so it has no pipelines or deployments. Enable it at {_LIB_FEATURES} → CI/CD.", |
| 229 | + ), |
| 230 | + ], |
| 231 | +) |
| 232 | +def test_project_without_deployments_is_skipped_with_a_warning( |
| 233 | + gitlab: respx.Router, access_levels: dict[str, str], warning: str |
| 234 | +) -> None: |
| 235 | + gitlab["group"].respond(json=[SERVICE, project(2, "team/lib", **access_levels)]) |
| 236 | + |
| 237 | + report: typing.Final = _collect() |
| 238 | + |
| 239 | + skipped: typing.Final = report.services[0] |
| 240 | + assert (skipped.project, skipped.rows, skipped.error, skipped.warnings) == ("team/lib", [], None, [warning]) |
| 241 | + assert len(report.services[1].rows) == 5 |
| 242 | + |
| 243 | + |
| 244 | +def test_enabled_or_private_features_are_collected(gitlab: respx.Router) -> None: |
| 245 | + gitlab["group"].respond( |
| 246 | + json=[project(1, "team/svc", environments_access_level="private", builds_access_level="enabled")] |
| 247 | + ) |
| 248 | + |
| 249 | + assert len(_only_service(_collect()).rows) == 5 |
| 250 | + |
| 251 | + |
| 252 | +def test_forbidden_group_stops_the_run(httpx2_mock: respx.Router) -> None: |
| 253 | + httpx2_mock.get(f"{API}/groups/team/projects").respond(403) |
| 254 | + |
| 255 | + with pytest.raises(AuthError, match=r"GitLab denied access to group 'team' \(403\)"): |
| 256 | + _collect() |
| 257 | + |
| 258 | + |
165 | 259 | def test_authentication_failure_stops_the_run(httpx2_mock: respx.Router) -> None: |
166 | 260 | httpx2_mock.get(f"{API}/groups/team/projects").respond(401) |
167 | 261 |
|
|
0 commit comments