142 lines
6.1 KiB
Python
142 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
class InfrastructureConfigTests(unittest.TestCase):
|
|
def test_root_compose_uses_only_infra_fragments(self) -> None:
|
|
compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8")
|
|
for fragment in (
|
|
"infra/compose/application.yml",
|
|
"nginx/docker-compose.yml",
|
|
"redis/docker-compose.yml",
|
|
"observability/docker-compose.yml",
|
|
"deployment/docker-compose.jobs.yml",
|
|
):
|
|
self.assertIn(fragment, compose)
|
|
self.assertNotIn("postgres:", compose.lower())
|
|
|
|
def test_only_nginx_fragment_publishes_ports(self) -> None:
|
|
forbidden = (
|
|
ROOT / "infra/compose/application.yml",
|
|
ROOT / "redis/docker-compose.yml",
|
|
ROOT / "observability/docker-compose.yml",
|
|
ROOT / "deployment/docker-compose.jobs.yml",
|
|
)
|
|
for path in forbidden:
|
|
self.assertNotIn("\n ports:", path.read_text(encoding="utf-8"), path)
|
|
nginx = (ROOT / "nginx/docker-compose.yml").read_text(encoding="utf-8")
|
|
self.assertEqual(nginx.count("\n ports:"), 1)
|
|
self.assertIn('NGINX_HTTP_PORT:-80}:80', nginx)
|
|
self.assertIn('NGINX_HTTPS_PORT:-443}:443', nginx)
|
|
|
|
def test_nginx_internal_denies_precede_spa(self) -> None:
|
|
site = (ROOT / "nginx/templates/site-tls.conf.template").read_text(encoding="utf-8")
|
|
internal = site.index("location ^~ /internal/")
|
|
api = site.index("location ^~ /api/")
|
|
frontend = site.index("include /etc/nginx/generated/frontend-location.conf")
|
|
self.assertLess(internal, api)
|
|
self.assertLess(api, frontend)
|
|
self.assertIn("location = /api/v1/realtime", site)
|
|
self.assertNotIn("message-safety:", site)
|
|
|
|
def test_redis_persistence_acl_and_no_host_port(self) -> None:
|
|
config = (ROOT / "redis/redis.conf").read_text(encoding="utf-8")
|
|
acl = (ROOT / "redis/users.acl.template").read_text(encoding="utf-8")
|
|
self.assertIn("appendonly yes", config)
|
|
self.assertIn("appendfsync everysec", config)
|
|
self.assertIn("save 900 1", config)
|
|
self.assertIn("user default off", acl)
|
|
self.assertIn("~han:api:*", acl)
|
|
self.assertIn("~han:safety:*", acl)
|
|
|
|
def test_otel_has_redaction_and_persistent_queue(self) -> None:
|
|
config = (ROOT / "observability/otel-collector.yaml").read_text(encoding="utf-8")
|
|
for forbidden_attribute in (
|
|
"http.request.header.authorization",
|
|
"http.request.header.cookie",
|
|
"url.query",
|
|
"db.statement",
|
|
"messaging.message.body",
|
|
):
|
|
self.assertIn(forbidden_attribute, config)
|
|
self.assertIn("storage: file_storage", config)
|
|
self.assertIn("retry_on_failure:", config)
|
|
|
|
def test_settings_cli_and_workers_are_deployable(self) -> None:
|
|
cli = ROOT / "api-backend/app/cli"
|
|
self.assertTrue((cli / "__init__.py").is_file())
|
|
self.assertTrue((cli / "seed_settings.py").is_file())
|
|
self.assertTrue((cli / "validate_settings.py").is_file())
|
|
|
|
jobs = (ROOT / "deployment/docker-compose.jobs.yml").read_text(encoding="utf-8")
|
|
self.assertIn("python -m app.cli.seed_settings", jobs)
|
|
self.assertIn("python -m app.cli.validate_settings", jobs)
|
|
|
|
application = (ROOT / "infra/compose/application.yml").read_text(encoding="utf-8")
|
|
self.assertIn("\n frontend-static:", application)
|
|
self.assertIn("frontend-test-site", application)
|
|
self.assertIn("frontend-static:/output", application)
|
|
for service, command in (
|
|
("delivery-worker:", "han-delivery-worker"),
|
|
("safety-recovery-worker:", "han-safety-worker"),
|
|
("cleanup-worker:", "han-cleanup-worker"),
|
|
):
|
|
self.assertIn(f"\n {service}", application)
|
|
self.assertIn(f'command: ["{command}"]', application)
|
|
self.assertNotIn("\n ports:", application)
|
|
nginx = (ROOT / "nginx/docker-compose.yml").read_text(encoding="utf-8")
|
|
self.assertIn("frontend-static: {condition: service_completed_successfully}", nginx)
|
|
|
|
def test_keycloak_management_health_and_bridge_environment(self) -> None:
|
|
standalone = (ROOT / "keycloak/docker-compose.yml").read_text(encoding="utf-8")
|
|
self.assertIn("GET /health/ready", standalone)
|
|
self.assertNotIn("GET /auth/health/ready", standalone)
|
|
|
|
application = (ROOT / "infra/compose/application.yml").read_text(encoding="utf-8")
|
|
for variable in (
|
|
"KEYCLOAK_OTP_MOCK_ENABLED",
|
|
"KEYCLOAK_OTP_MOCK_CODE",
|
|
"KEYCLOAK_OTP_HMAC_KEY",
|
|
"KEYCLOAK_OTP_TTL_SEC",
|
|
"KEYCLOAK_OTP_MAX_VERIFY_ATTEMPTS",
|
|
"KEYCLOAK_OTP_SETTINGS_MAX_STALE_SEC",
|
|
"KEYCLOAK_SETTINGS_BRIDGE_URL",
|
|
"KEYCLOAK_SETTINGS_BRIDGE_TOKEN",
|
|
):
|
|
self.assertIn(f" {variable}:", application)
|
|
|
|
def test_env_validator_accepts_materialized_example(self) -> None:
|
|
example = (ROOT / ".env.example").read_text(encoding="utf-8")
|
|
for required in (
|
|
"CURSOR_HMAC_SECRET=",
|
|
"BITRIX_TOKEN_ENCRYPTION_KEY=",
|
|
"KEYCLOAK_INTERNAL_URL=http://keycloak:8080/auth",
|
|
):
|
|
self.assertIn(required, example)
|
|
materialized = example.replace("change-me", "0123456789abcdef0123456789abcdef")
|
|
materialized = materialized.replace(
|
|
"KEYCLOAK_OTP_MOCK_RISK_ACCEPTED=false",
|
|
"KEYCLOAK_OTP_MOCK_RISK_ACCEPTED=true",
|
|
)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
env_file = Path(directory) / ".env"
|
|
env_file.write_text(materialized, encoding="utf-8")
|
|
result = subprocess.run(
|
|
[sys.executable, str(ROOT / "scripts/validate-env"), str(env_file)],
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|