135 lines
6.3 KiB
Python
135 lines
6.3 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from cryptography.fernet import Fernet
|
|
from pydantic import ValidationError
|
|
|
|
from govoplan_core.core.install_config import env_template, validate_runtime_configuration
|
|
from govoplan_core.settings import Settings
|
|
|
|
|
|
class InstallConfigTests(unittest.TestCase):
|
|
def test_calendar_outbox_retention_setting_is_validated(self) -> None:
|
|
self.assertEqual(Settings().calendar_outbox_terminal_retention_days, 90)
|
|
self.assertEqual(
|
|
Settings(
|
|
CALENDAR_OUTBOX_TERMINAL_RETENTION_DAYS="0"
|
|
).calendar_outbox_terminal_retention_days,
|
|
0,
|
|
)
|
|
with self.assertRaises(ValidationError):
|
|
Settings(CALENDAR_OUTBOX_TERMINAL_RETENTION_DAYS="-1")
|
|
|
|
def test_self_hosted_validation_reports_actionable_missing_settings(self) -> None:
|
|
result = validate_runtime_configuration({}, profile="self-hosted")
|
|
|
|
self.assertFalse(result.ok)
|
|
errors = {issue.key: issue for issue in result.errors}
|
|
self.assertIn("APP_ENV", errors)
|
|
self.assertIn("DATABASE_URL", errors)
|
|
self.assertIn("MASTER_KEY_B64", errors)
|
|
self.assertIn("ENABLED_MODULES", errors)
|
|
self.assertIn("CORS_ORIGINS", errors)
|
|
self.assertIn("Set DATABASE_URL", errors["DATABASE_URL"].action)
|
|
|
|
def test_self_hosted_validation_accepts_explicit_postgres_configuration(self) -> None:
|
|
result = validate_runtime_configuration(
|
|
{
|
|
"APP_ENV": "production",
|
|
"GOVOPLAN_INSTALL_PROFILE": "self-hosted",
|
|
"DATABASE_URL": "postgresql+psycopg://govoplan:secret@db.example.internal:5432/govoplan",
|
|
"GOVOPLAN_DATABASE_URL_PGTOOLS": "postgresql://govoplan:secret@db.example.internal:5432/govoplan",
|
|
"MASTER_KEY_B64": Fernet.generate_key().decode("ascii"),
|
|
"ENABLED_MODULES": "access,admin,policy,audit",
|
|
"CELERY_ENABLED": "true",
|
|
"REDIS_URL": "redis://redis.example.internal:6379/0",
|
|
"CORS_ORIGINS": "https://govoplan.example.org",
|
|
"GOVOPLAN_TRUSTED_HOSTS": "govoplan.example.org",
|
|
"AUTH_COOKIE_SECURE": "true",
|
|
"GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS": "false",
|
|
"FILE_STORAGE_BACKEND": "local",
|
|
"FILE_STORAGE_LOCAL_ROOT": "/var/lib/govoplan/files",
|
|
},
|
|
profile="self-hosted",
|
|
)
|
|
|
|
self.assertTrue(result.ok, result.to_text())
|
|
self.assertEqual([], [issue.key for issue in result.errors])
|
|
|
|
def test_production_validation_rejects_dev_bootstrap_and_sqlite(self) -> None:
|
|
result = validate_runtime_configuration(
|
|
{
|
|
"APP_ENV": "production",
|
|
"DATABASE_URL": "sqlite:////tmp/govoplan.db",
|
|
"MASTER_KEY_B64": Fernet.generate_key().decode("ascii"),
|
|
"ENABLED_MODULES": "access,admin",
|
|
"DEV_BOOTSTRAP_ENABLED": "true",
|
|
"CORS_ORIGINS": "https://govoplan.example.org",
|
|
"AUTH_COOKIE_SECURE": "true",
|
|
"FILE_STORAGE_BACKEND": "local",
|
|
"FILE_STORAGE_LOCAL_ROOT": "/var/lib/govoplan/files",
|
|
},
|
|
profile="self-hosted",
|
|
)
|
|
|
|
error_keys = {issue.key for issue in result.errors}
|
|
self.assertIn("DATABASE_URL", error_keys)
|
|
self.assertIn("DEV_BOOTSTRAP_ENABLED", error_keys)
|
|
|
|
def test_production_validation_rejects_wildcard_host_and_proxy_trust(self) -> None:
|
|
result = validate_runtime_configuration(
|
|
{
|
|
"APP_ENV": "production",
|
|
"GOVOPLAN_TRUSTED_HOSTS": "*",
|
|
"FORWARDED_ALLOW_IPS": "*",
|
|
"GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS": "false",
|
|
},
|
|
profile="self-hosted",
|
|
)
|
|
|
|
error_keys = {issue.key for issue in result.errors}
|
|
self.assertIn("GOVOPLAN_TRUSTED_HOSTS", error_keys)
|
|
self.assertIn("FORWARDED_ALLOW_IPS", error_keys)
|
|
|
|
def test_connector_deployment_allowlists_require_exact_names_and_absolute_paths(self) -> None:
|
|
result = validate_runtime_configuration(
|
|
{
|
|
"APP_ENV": "development",
|
|
"GOVOPLAN_CONNECTOR_SECRET_ENV_ALLOWLIST": "VALID_SECRET,invalid-name",
|
|
"GOVOPLAN_CONNECTOR_CA_BUNDLE_ALLOWLIST": "relative/connector-ca.pem",
|
|
},
|
|
profile="development",
|
|
)
|
|
|
|
error_keys = {issue.key for issue in result.errors}
|
|
self.assertIn("GOVOPLAN_CONNECTOR_SECRET_ENV_ALLOWLIST", error_keys)
|
|
self.assertIn("GOVOPLAN_CONNECTOR_CA_BUNDLE_ALLOWLIST", error_keys)
|
|
|
|
def test_env_templates_surface_install_profiles(self) -> None:
|
|
self_hosted = env_template(profile="self-hosted")
|
|
production_like = env_template(profile="production-like", generate_secrets=True)
|
|
|
|
self.assertIn("GOVOPLAN_INSTALL_PROFILE=self-hosted", self_hosted)
|
|
self.assertIn("MASTER_KEY_B64=<generate", self_hosted)
|
|
self.assertIn("CALENDAR_OUTBOX_TERMINAL_RETENTION_DAYS=90", self_hosted)
|
|
self.assertIn("GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS=false", self_hosted)
|
|
self.assertIn("GOVOPLAN_CONNECTOR_SECRET_ENV_ALLOWLIST=", self_hosted)
|
|
self.assertIn("GOVOPLAN_CONNECTOR_CA_BUNDLE_ALLOWLIST=", self_hosted)
|
|
self.assertIn("GOVOPLAN_TRUSTED_HOSTS=govoplan.example.org", self_hosted)
|
|
self.assertIn("GOVOPLAN_HTTP_HSTS_SECONDS=31536000", self_hosted)
|
|
self.assertIn("AUTH_LOGIN_THROTTLE_IDENTITY_LIMIT=10", self_hosted)
|
|
self.assertIn("GOVOPLAN_INSTALL_PROFILE=production-like", production_like)
|
|
self.assertNotIn("MASTER_KEY_B64=<generate", production_like)
|
|
self.assertIn("CALENDAR_OUTBOX_TERMINAL_RETENTION_DAYS=90", production_like)
|
|
self.assertIn("GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS=true", production_like)
|
|
self.assertIn("GOVOPLAN_CONNECTOR_SECRET_ENV_ALLOWLIST=", production_like)
|
|
self.assertIn("GOVOPLAN_CONNECTOR_CA_BUNDLE_ALLOWLIST=", production_like)
|
|
self.assertIn("GOVOPLAN_TRUSTED_HOSTS=127.0.0.1,localhost,testserver", production_like)
|
|
self.assertIn("GOVOPLAN_HTTP_HSTS_SECONDS=0", production_like)
|
|
self.assertIn("AUTH_LOGIN_THROTTLE_IDENTITY_LIMIT=10", production_like)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|