Files
stack/tests/api/test_deploy.py
kert bd8cda1931
Some checks failed
CI / skinny-install (aco) (push) Successful in 1m14s
CI / skinny-install (bib) (push) Successful in 5m47s
CI / skinny-install (api) (push) Successful in 37s
CI / skinny-install (bcda) (push) Successful in 37s
CI / skinny-install (bls) (push) Successful in 40s
CI / skinny-install (ccw) (push) Successful in 41s
CI / skinny-install (cli) (push) Successful in 42s
CI / skinny-install (cms) (push) Successful in 39s
CI / skinny-install (conf) (push) Successful in 41s
CI / skinny-install (opps) (push) Successful in 39s
CI / skinny-install (perf) (push) Successful in 40s
CI / skinny-install (pfs) (push) Successful in 50s
CI / skinny-install (rex) (push) Successful in 33s
Deploy / build-scan-report (push) Failing after 12m6s
Infra CI / notebooks (push) Successful in 22s
Infra CI / zotero (push) Successful in 12s
Infra CI / docs (push) Successful in 11s
Infra CI / api (push) Successful in 12s
Infra CI / mc (push) Successful in 22s
CI / lint-test (push) Failing after 41m1s
remove: scrub all woodpecker references from codebase
Clean 17 files across src/, tests/, dev/, stack.toml, deploy.sh:
- api/auth/provision.py: remove WoodpeckerClient, provision_woodpecker,
  _get_woodpecker_token, woodpecker field from ProvisionResult
- api/auth/manifest.py: remove woodpecker from CREDENTIALS + Provisioner
- api/diag: remove woodpecker log fetching
- sem/hooks.py: remove woodpecker sync step
- stack.toml: remove [services.woodpecker] config
- deploy.sh: remove woodpecker deploy steps
- dev/scripts: remove woodpecker from config gen, secrets, readme
- tests: remove all woodpecker assertions and test cases

Zero woodpecker references remain in the codebase.
2026-04-18 18:51:21 -04:00

166 lines
5.4 KiB
Python

"""Tests for api.auth.deploy — two-phase credential rotation."""
from __future__ import annotations
import subprocess
from pathlib import Path
from unittest.mock import MagicMock, patch
from api.auth.deploy import (
_set_env_var,
deploy,
verify_all,
verify_gitea,
verify_postgres,
verify_rustfs,
)
from api.auth.provision import derive_all
ROOT = bytes.fromhex("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
COMMIT = "abc1234"
class TestVerifyPostgres:
def test_returns_empty_on_success(self):
values = derive_all(ROOT, COMMIT)
with patch("api.auth.deploy.subprocess.run") as mock:
mock.return_value = subprocess.CompletedProcess([], 0)
errors = verify_postgres(values, container="pg")
assert errors == []
def test_returns_errors_on_failure(self):
values = derive_all(ROOT, COMMIT)
with patch("api.auth.deploy.subprocess.run") as mock:
mock.return_value = subprocess.CompletedProcess([], 1, stderr="auth failed")
errors = verify_postgres(values, container="pg")
assert len(errors) > 0
assert "auth failed" in errors[0]
class TestVerifyGitea:
def test_returns_empty_on_success(self):
values = {"GITEA_TOKEN": "tok"}
mock_resp = MagicMock(status_code=200)
with patch("api.auth.deploy.httpx.get", return_value=mock_resp):
errors = verify_gitea(values)
assert errors == []
def test_returns_error_on_401(self):
values = {"GITEA_TOKEN": "bad"}
mock_resp = MagicMock(status_code=401)
with patch("api.auth.deploy.httpx.get", return_value=mock_resp):
errors = verify_gitea(values)
assert len(errors) == 1
assert "401" in errors[0]
def test_returns_error_on_no_token(self):
errors = verify_gitea({})
assert len(errors) == 1
assert "no GITEA_TOKEN" in errors[0]
class TestVerifyRustfs:
def test_returns_empty_on_success(self):
mock_resp = MagicMock(status_code=200)
with patch("api.auth.deploy.httpx.get", return_value=mock_resp):
errors = verify_rustfs()
assert errors == []
def test_returns_error_on_failure(self):
with patch("api.auth.deploy.httpx.get", side_effect=Exception("down")):
errors = verify_rustfs()
assert len(errors) == 1
class TestVerifyAll:
def test_aggregates_errors(self):
values = derive_all(ROOT, COMMIT)
values["GITEA_TOKEN"] = "tok"
with (
patch("api.auth.deploy.verify_postgres", return_value=["pg: fail"]),
patch("api.auth.deploy.verify_gitea", return_value=[]),
patch("api.auth.deploy.verify_rustfs", return_value=["rustfs: fail"]),
):
errors = verify_all(values)
assert len(errors) == 2
class TestSetEnvVar:
def test_updates_existing(self, tmp_path: Path):
p = tmp_path / ".env"
p.write_text("A=1\nB=2\n")
_set_env_var(p, "B", "99")
assert "B=99" in p.read_text()
assert "B=2" not in p.read_text()
def test_appends_new(self, tmp_path: Path):
p = tmp_path / ".env"
p.write_text("A=1\n")
_set_env_var(p, "B", "2")
content = p.read_text()
assert "A=1" in content
assert "B=2" in content
class TestDeploy:
def test_two_phase_rotation(self, tmp_path: Path):
env = tmp_path / ".env"
env.write_text("OLD_KEY=keep\n")
with (
# provision_postgres no longer called
patch("api.auth.deploy.provision_gitea", return_value="new-tok"),
patch("api.auth.deploy.subprocess.run"),
patch("api.auth.deploy.verify_all", return_value=[]),
patch("api.auth.deploy.time.sleep"),
):
result = deploy(ROOT, COMMIT, env, compose_dir=tmp_path)
assert result.ok
assert result.postgres
assert result.gitea
assert result.env_written
content = env.read_text()
assert "GITEA_TOKEN=new-tok" in content
assert "OLD_KEY=keep" in content
def test_rollback_on_health_failure(self, tmp_path: Path):
env = tmp_path / ".env"
env.write_text("SAFE=original\n")
with (
# provision_postgres no longer called
patch("api.auth.deploy.provision_gitea", return_value="tok"),
patch("api.auth.deploy.subprocess.run"),
patch(
"api.auth.deploy.verify_all",
return_value=["pg: auth failed"],
),
patch("api.auth.deploy.time.sleep"),
):
result = deploy(ROOT, COMMIT, env, compose_dir=tmp_path)
assert not result.ok
# .env should be rolled back to original
content = env.read_text()
assert "SAFE=original" in content
def test_gitea_failure_still_deploys(self, tmp_path: Path):
env = tmp_path / ".env"
env.write_text("GITEA_TOKEN=old\n")
with (
patch(
"api.auth.deploy.provision_gitea",
side_effect=Exception("gitea down"),
),
patch("api.auth.deploy.subprocess.run"),
patch("api.auth.deploy.verify_all", return_value=[]),
patch("api.auth.deploy.time.sleep"),
):
result = deploy(ROOT, COMMIT, env, compose_dir=tmp_path)
assert not result.gitea
assert result.postgres # preserved, not rotated
assert result.env_written