Files
jobtrackingapp/scripts/test-ingress.py
T
cesnimda 97fce89c00
CI and Deploy / test (push) Failing after 2m23s
CI and Deploy / deploy (push) Has been skipped
fix(ci): bootstrap Compose without a GitHub token
2026-09-09 18:35:45 +02:00

80 lines
4.3 KiB
Python

"""Real Compose rendering plus adversarial production-config mutations; stdlib only."""
import copy
import importlib.util
import json
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
spec = importlib.util.spec_from_file_location("ingress", ROOT / "scripts/validate-ingress.py")
ingress = importlib.util.module_from_spec(spec)
spec.loader.exec_module(ingress)
class IngressConfigTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
env = dict(os.environ, AI_SERVICE_TOKEN="synthetic-b06", APP_PUBLIC_BASE_URL="https://jobs.example.test",
INGRESS_PROXY_CIDRS="192.0.2.10/32", WEB_PROXY_SUBNET="172.31.250.0/29",
NEXT_PUBLIC_API_BASE_URL="/api")
with tempfile.NamedTemporaryFile(mode="w", suffix=".env", delete=False) as file:
file.write("# No repository secrets are read by this fixture.\n")
path = file.name
try:
# CI installs standalone Compose; developer machines can use the Docker plugin.
command = ["docker", "compose"] if shutil.which("docker") else [shutil.which("compose") or "docker-compose"]
base = command + ["--env-file", path, "-f", "docker-compose.yml"]
def render(extra):
result = subprocess.run(base + extra + ["--profile", "bundled-ollama", "config", "--format", "json"],
cwd=ROOT, env=env, capture_output=True, text=True, timeout=30, check=True)
return json.loads(result.stdout)
cls.production = render([])
cls.development = render(["-f", "docker-compose.dev.yml"])
finally:
Path(path).unlink()
def test_effective_production_is_safe(self):
ingress.validate(self.production)
def test_development_remains_explicit_and_cannot_pass_production_preflight(self):
self.assertEqual(self.development["services"]["backend"]["ports"][0]["published"], "5202")
with self.assertRaises(ValueError):
ingress.validate(self.development)
def test_public_internal_ports_and_trust_bypasses_fail_closed(self):
def change(config, service, key, value):
config["services"][service][key] = value
mutations = [
lambda c: change(c, "backend", "ports", [{"target": 8080, "published": "5202"}]),
lambda c: change(c, "ai-service", "ports", [{"target": 8001, "published": "8001"}]),
lambda c: change(c, "ollama", "network_mode", "host"),
lambda c: c["services"]["frontend"]["ports"].append({"target": 80, "published": "8081"}),
lambda c: c["services"]["frontend"]["environment"].update(INGRESS_MODE="direct"),
lambda c: c["services"]["frontend"]["environment"].update(INGRESS_PROXY_CIDRS=""),
lambda c: c["services"]["frontend"]["environment"].update(INGRESS_PROXY_CIDRS="0.0.0.0/0"),
lambda c: c["services"]["frontend"]["environment"].update(INGRESS_PROXY_CIDRS="::/0"),
lambda c: c["services"]["frontend"]["environment"].update(INGRESS_PROXY_CIDRS="::/64"),
lambda c: c["services"]["frontend"]["environment"].update(INGRESS_PROXY_CIDRS="::ffff:0:0/96"),
lambda c: c["services"]["backend"]["environment"].update(ASPNETCORE_ENVIRONMENT="Development"),
lambda c: c["services"]["backend"]["environment"].update(DOTNET_ENVIRONMENT="Development"),
lambda c: c["services"]["backend"]["environment"].update(ASPNETCORE_FORWARDEDHEADERS_ENABLED="true"),
lambda c: c["services"]["backend"]["environment"].update(Proxy__KnownNetworks__0="0.0.0.0/0"),
lambda c: c["networks"]["web_proxy"].update(internal=False),
lambda c: c["services"]["ai-service"]["networks"].update(web_proxy={}),
lambda c: c["services"]["frontend"]["build"]["args"].update(NEXT_PUBLIC_API_BASE_URL="http://localhost:5202/api"),
]
for index, mutation in enumerate(mutations):
with self.subTest(mutation=index):
config = copy.deepcopy(self.production)
mutation(config)
with self.assertRaises(ValueError):
ingress.validate(config)
if __name__ == "__main__":
unittest.main()