104 lines
4.3 KiB
Python
104 lines
4.3 KiB
Python
"""drone — SCM-configured functional test (phase drone).
|
|
|
|
Proves that drone is wired to the per-run gitea dep, not just healthy.
|
|
The negative control: a drone deployed WITHOUT DRONE_GITEA_CLIENT_ID + DRONE_GITEA_SERVER
|
|
(i.e., without compose.gitea.yml) would NOT redirect /login to the gitea dep's OAuth
|
|
authorize endpoint — it would error or redirect elsewhere. This test is therefore falsified
|
|
by a misconfigured drone.
|
|
|
|
Test: GET https://<drone>/login must issue a 303 redirect whose Location header points to
|
|
the per-run gitea dep's /login/oauth/authorize URL. We capture ONLY drone's first redirect
|
|
(not gitea's subsequent redirect to /user/login for unauthenticated users).
|
|
|
|
Per ADV-drone-01: following all redirects causes the assertion to land on gitea's /user/login
|
|
(200 OK after gitea redirects unauthenticated users away from /login/oauth/authorize), which
|
|
means the path assertion always fails. The fix is a no-follow handler that captures the
|
|
Location header from drone's 303 directly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ssl
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
|
|
import pytest
|
|
|
|
|
|
class _CaptureOneRedirect(urllib.request.HTTPRedirectHandler):
|
|
"""Stop redirect-following after the FIRST hop; raise HTTPError so the caller can inspect
|
|
the Location header from drone's 303 without following gitea's subsequent redirects."""
|
|
|
|
def http_error_302(self, req, fp, code, msg, headers):
|
|
raise urllib.error.HTTPError(req.full_url, code, msg, headers, fp)
|
|
|
|
http_error_303 = http_error_302
|
|
|
|
|
|
@pytest.mark.requires_deps
|
|
def test_login_redirects_to_gitea_dep(live_app, deps):
|
|
"""Drone's /login must issue a 303 redirect to the per-run gitea dep's OAuth2 authorize
|
|
endpoint.
|
|
|
|
Proves: (a) gitea is the SCM backend (not github or unconfigured); (b) the OAuth2
|
|
client_id in the Location header matches the app the harness created in the dep gitea
|
|
instance; (c) the redirect targets the TEST-RUN gitea, not any hardcoded external provider.
|
|
|
|
ADV-drone-01 fix: only drone's first 303 is captured; gitea's own redirects (unauthenticated
|
|
user → /user/login) are not followed, so the path assertion is against the correct URL.
|
|
"""
|
|
assert "gitea" in deps, (
|
|
f"gitea dep not in deps — dep provisioning should have populated this. "
|
|
f"Got keys: {list(deps.keys())}"
|
|
)
|
|
gitea = deps["gitea"]
|
|
gitea_domain: str = gitea["domain"]
|
|
expected_client_id: str = gitea["client_id"]
|
|
|
|
ctx = ssl.create_default_context()
|
|
ctx.check_hostname = False
|
|
ctx.verify_mode = ssl.CERT_NONE
|
|
|
|
opener = urllib.request.build_opener(
|
|
_CaptureOneRedirect(),
|
|
urllib.request.HTTPSHandler(context=ctx),
|
|
)
|
|
|
|
redirect_url = None
|
|
try:
|
|
opener.open(f"https://{live_app}/login", timeout=30)
|
|
pytest.fail(
|
|
f"Expected a 302/303 redirect from https://{live_app}/login but got 200 OK — "
|
|
f"drone may not have gitea SCM configured (check COMPOSE_FILE + GITEA_DOMAIN)"
|
|
)
|
|
except urllib.error.HTTPError as e:
|
|
if e.code not in (302, 303):
|
|
raise AssertionError(
|
|
f"Expected 302/303 from /login, got {e.code} — "
|
|
f"drone may not have gitea SCM configured"
|
|
) from e
|
|
redirect_url = e.headers.get("Location") or e.headers.get("location", "")
|
|
|
|
assert redirect_url, (
|
|
"Drone /login returned a redirect but Location header is empty — "
|
|
"check drone gitea SCM configuration"
|
|
)
|
|
|
|
parsed = urllib.parse.urlparse(redirect_url)
|
|
assert parsed.scheme == "https", f"Redirect Location has unexpected scheme: {redirect_url!r}"
|
|
assert parsed.netloc == gitea_domain, (
|
|
f"Drone /login did not redirect to the gitea dep ({gitea_domain!r}); "
|
|
f"Location: {redirect_url!r} — check GITEA_DOMAIN + COMPOSE_FILE in drone's .env"
|
|
)
|
|
assert parsed.path == "/login/oauth/authorize", (
|
|
f"Redirect path is {parsed.path!r}, expected /login/oauth/authorize — "
|
|
f"drone may not have gitea SCM configured"
|
|
)
|
|
params = urllib.parse.parse_qs(parsed.query)
|
|
actual_client_id = params.get("client_id", [None])[0]
|
|
assert actual_client_id == expected_client_id, (
|
|
f"OAuth2 client_id mismatch: drone is using {actual_client_id!r} but the harness "
|
|
f"created app {expected_client_id!r} in the dep gitea — check install_steps.sh"
|
|
)
|