项目文件夹

文件
wehub-resource-sync 9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:31:33 +08:00

146 行
5.3 KiB
Python

"""Tests for routers/tailscale.py — the proxy in front of the host-agent's
Tailscale status endpoint.
Mocked surfaces:
* urllib.request.urlopen — stand-in for the host-agent HTTP call.
The actual container/native `tailscale status --json` behavior lives at the
host-agent layer and is not reproducible here.
"""
import json
from unittest.mock import patch, MagicMock
import urllib.error
# ---------------------------------------------------------------------------
# Auth enforcement
# ---------------------------------------------------------------------------
def test_tailscale_status_requires_auth(test_client):
resp = test_client.get("/api/tailscale/status")
assert resp.status_code == 401
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _mock_agent_response(body, status=200):
mock_resp = MagicMock()
mock_resp.status = status
mock_resp.read = MagicMock(return_value=json.dumps(body).encode("utf-8"))
mock_resp.__enter__ = MagicMock(return_value=mock_resp)
mock_resp.__exit__ = MagicMock(return_value=False)
return mock_resp
def _mock_agent_http_error(status, body):
err = urllib.error.HTTPError(
url="http://agent/v1/tailscale/status",
code=status,
msg="error",
hdrs=None,
fp=None,
)
err.read = lambda: json.dumps(body).encode("utf-8")
return err
# ---------------------------------------------------------------------------
# Three normal status shapes (all 200)
# ---------------------------------------------------------------------------
def test_status_when_extension_not_running(test_client):
"""Container not started → running=false. Not an error — the user just
hasn't enabled the extension yet."""
upstream = {"running": False}
with patch("routers.tailscale.urllib.request.urlopen", return_value=_mock_agent_response(upstream)):
resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
assert resp.status_code == 200
body = resp.json()
assert body["running"] is False
def test_status_when_running_but_not_authenticated(test_client):
"""Container up but TS_AUTHKEY is empty / rejected → user-facing reason."""
upstream = {
"running": True,
"authenticated": False,
"reason": "Tailscale is running but not yet authenticated. Set TS_AUTHKEY and restart.",
}
with patch("routers.tailscale.urllib.request.urlopen", return_value=_mock_agent_response(upstream)):
resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
assert resp.status_code == 200
body = resp.json()
assert body["running"] is True
assert body["authenticated"] is False
assert "TS_AUTHKEY" in body["reason"]
def test_status_when_fully_joined(test_client):
"""Happy path — device is on the tailnet."""
upstream = {
"running": True,
"authenticated": True,
"backend_state": "Running",
"self": {
"hostname": "ods",
"dns_name": "ods.tail-abcde.ts.net",
"ips": ["100.64.0.42", "fd7a:115c:a1e0::42"],
"online": True,
},
"magic_dns_suffix": "tail-abcde.ts.net",
"tailnet_name": "example.com",
}
with patch("routers.tailscale.urllib.request.urlopen", return_value=_mock_agent_response(upstream)):
resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
assert resp.status_code == 200
body = resp.json()
assert body["authenticated"] is True
assert body["self"]["dns_name"].endswith(".ts.net")
assert "100.64.0.42" in body["self"]["ips"]
assert body["magic_dns_suffix"] == "tail-abcde.ts.net"
# ---------------------------------------------------------------------------
# Error translation
# ---------------------------------------------------------------------------
def test_status_returns_503_when_agent_unreachable(test_client):
with patch(
"routers.tailscale.urllib.request.urlopen",
side_effect=urllib.error.URLError("connection refused"),
):
resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
assert resp.status_code == 503
def test_status_returns_504_when_agent_request_times_out(test_client):
with patch(
"routers.tailscale.urllib.request.urlopen",
side_effect=TimeoutError("timed out"),
):
resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
assert resp.status_code == 504
def test_status_passes_through_504_timeout(test_client):
err = _mock_agent_http_error(504, {"error": "docker exec timed out"})
with patch("routers.tailscale.urllib.request.urlopen", side_effect=err):
resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
assert resp.status_code == 504
assert "timed out" in resp.json()["detail"]
def test_status_passes_through_500_when_agent_errors(test_client):
err = _mock_agent_http_error(500, {"error": "docker exec failed: ENOENT"})
with patch("routers.tailscale.urllib.request.urlopen", side_effect=err):
resp = test_client.get("/api/tailscale/status", headers=test_client.auth_headers)
assert resp.status_code == 500