fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
325
backend/test_environment_config.py
Normal file
325
backend/test_environment_config.py
Normal file
@@ -0,0 +1,325 @@
|
||||
"""
|
||||
Tests for Dev/Staging/Prod environment configuration.
|
||||
|
||||
Tests the environment files, Docker Compose configurations, and helper scripts
|
||||
to ensure proper environment separation and functionality.
|
||||
|
||||
Usage:
|
||||
cd backend && pytest test_environment_config.py -v
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
import pytest
|
||||
|
||||
|
||||
# Project root directory
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
|
||||
|
||||
class TestEnvironmentFiles:
|
||||
"""Test environment configuration files exist and have correct content."""
|
||||
|
||||
def test_env_dev_exists(self):
|
||||
"""Test that .env.dev file exists."""
|
||||
env_file = PROJECT_ROOT / ".env.dev"
|
||||
assert env_file.exists(), f".env.dev should exist at {env_file}"
|
||||
|
||||
def test_env_staging_exists(self):
|
||||
"""Test that .env.staging file exists."""
|
||||
env_file = PROJECT_ROOT / ".env.staging"
|
||||
assert env_file.exists(), f".env.staging should exist at {env_file}"
|
||||
|
||||
def test_env_example_exists(self):
|
||||
"""Test that .env.example file exists."""
|
||||
env_file = PROJECT_ROOT / ".env.example"
|
||||
assert env_file.exists(), f".env.example should exist at {env_file}"
|
||||
|
||||
def test_env_dev_content(self):
|
||||
"""Test that .env.dev has correct environment settings."""
|
||||
env_file = PROJECT_ROOT / ".env.dev"
|
||||
if not env_file.exists():
|
||||
pytest.skip(".env.dev not found")
|
||||
|
||||
content = env_file.read_text()
|
||||
|
||||
# Required settings for development
|
||||
assert "ENVIRONMENT=development" in content, "ENVIRONMENT should be development"
|
||||
assert "COMPOSE_PROJECT_NAME=breakpilot-dev" in content, "Project name should be breakpilot-dev"
|
||||
assert "POSTGRES_DB=breakpilot_dev" in content, "Database should be breakpilot_dev"
|
||||
assert "DEBUG=true" in content, "DEBUG should be true in development"
|
||||
|
||||
def test_env_staging_content(self):
|
||||
"""Test that .env.staging has correct environment settings."""
|
||||
env_file = PROJECT_ROOT / ".env.staging"
|
||||
if not env_file.exists():
|
||||
pytest.skip(".env.staging not found")
|
||||
|
||||
content = env_file.read_text()
|
||||
|
||||
# Required settings for staging
|
||||
assert "ENVIRONMENT=staging" in content, "ENVIRONMENT should be staging"
|
||||
assert "COMPOSE_PROJECT_NAME=breakpilot-staging" in content, "Project name should be breakpilot-staging"
|
||||
assert "POSTGRES_DB=breakpilot_staging" in content, "Database should be breakpilot_staging"
|
||||
assert "DEBUG=false" in content, "DEBUG should be false in staging"
|
||||
|
||||
def test_env_files_have_different_databases(self):
|
||||
"""Test that dev and staging use different database names."""
|
||||
env_dev = PROJECT_ROOT / ".env.dev"
|
||||
env_staging = PROJECT_ROOT / ".env.staging"
|
||||
|
||||
if not (env_dev.exists() and env_staging.exists()):
|
||||
pytest.skip("Environment files not found")
|
||||
|
||||
dev_content = env_dev.read_text()
|
||||
staging_content = env_staging.read_text()
|
||||
|
||||
# Extract database names
|
||||
dev_db = None
|
||||
staging_db = None
|
||||
for line in dev_content.split("\n"):
|
||||
if line.startswith("POSTGRES_DB="):
|
||||
dev_db = line.split("=")[1].strip()
|
||||
for line in staging_content.split("\n"):
|
||||
if line.startswith("POSTGRES_DB="):
|
||||
staging_db = line.split("=")[1].strip()
|
||||
|
||||
assert dev_db is not None, "POSTGRES_DB not found in .env.dev"
|
||||
assert staging_db is not None, "POSTGRES_DB not found in .env.staging"
|
||||
assert dev_db != staging_db, f"Dev and staging should use different databases, both use {dev_db}"
|
||||
|
||||
|
||||
class TestDockerComposeFiles:
|
||||
"""Test Docker Compose configuration files."""
|
||||
|
||||
def test_docker_compose_yml_exists(self):
|
||||
"""Test that main docker-compose.yml exists."""
|
||||
compose_file = PROJECT_ROOT / "docker-compose.yml"
|
||||
assert compose_file.exists(), "docker-compose.yml should exist"
|
||||
|
||||
def test_docker_compose_override_exists(self):
|
||||
"""Test that docker-compose.override.yml exists for dev."""
|
||||
compose_file = PROJECT_ROOT / "docker-compose.override.yml"
|
||||
assert compose_file.exists(), "docker-compose.override.yml should exist for development"
|
||||
|
||||
def test_docker_compose_staging_exists(self):
|
||||
"""Test that docker-compose.staging.yml exists."""
|
||||
compose_file = PROJECT_ROOT / "docker-compose.staging.yml"
|
||||
assert compose_file.exists(), "docker-compose.staging.yml should exist"
|
||||
|
||||
def test_docker_compose_override_valid_yaml(self):
|
||||
"""Test that docker-compose.override.yml has valid syntax."""
|
||||
compose_file = PROJECT_ROOT / "docker-compose.override.yml"
|
||||
if not compose_file.exists():
|
||||
pytest.skip("docker-compose.override.yml not found")
|
||||
|
||||
result = subprocess.run(
|
||||
["docker", "compose", "-f", "docker-compose.yml", "-f", "docker-compose.override.yml", "config"],
|
||||
cwd=PROJECT_ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, f"docker-compose.override.yml syntax error: {result.stderr}"
|
||||
|
||||
def test_docker_compose_staging_valid_yaml(self):
|
||||
"""Test that docker-compose.staging.yml has valid syntax."""
|
||||
compose_file = PROJECT_ROOT / "docker-compose.staging.yml"
|
||||
if not compose_file.exists():
|
||||
pytest.skip("docker-compose.staging.yml not found")
|
||||
|
||||
result = subprocess.run(
|
||||
["docker", "compose", "-f", "docker-compose.yml", "-f", "docker-compose.staging.yml", "config"],
|
||||
cwd=PROJECT_ROOT,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, f"docker-compose.staging.yml syntax error: {result.stderr}"
|
||||
|
||||
|
||||
class TestHelperScripts:
|
||||
"""Test helper scripts for environment management."""
|
||||
|
||||
def test_env_switch_script_exists(self):
|
||||
"""Test that env-switch.sh script exists."""
|
||||
script = PROJECT_ROOT / "scripts" / "env-switch.sh"
|
||||
assert script.exists(), "scripts/env-switch.sh should exist"
|
||||
|
||||
def test_env_switch_script_executable(self):
|
||||
"""Test that env-switch.sh is executable."""
|
||||
script = PROJECT_ROOT / "scripts" / "env-switch.sh"
|
||||
if not script.exists():
|
||||
pytest.skip("env-switch.sh not found")
|
||||
assert os.access(script, os.X_OK), "env-switch.sh should be executable"
|
||||
|
||||
def test_start_script_exists(self):
|
||||
"""Test that start.sh script exists."""
|
||||
script = PROJECT_ROOT / "scripts" / "start.sh"
|
||||
assert script.exists(), "scripts/start.sh should exist"
|
||||
|
||||
def test_start_script_executable(self):
|
||||
"""Test that start.sh is executable."""
|
||||
script = PROJECT_ROOT / "scripts" / "start.sh"
|
||||
if not script.exists():
|
||||
pytest.skip("start.sh not found")
|
||||
assert os.access(script, os.X_OK), "start.sh should be executable"
|
||||
|
||||
def test_stop_script_exists(self):
|
||||
"""Test that stop.sh script exists."""
|
||||
script = PROJECT_ROOT / "scripts" / "stop.sh"
|
||||
assert script.exists(), "scripts/stop.sh should exist"
|
||||
|
||||
def test_stop_script_executable(self):
|
||||
"""Test that stop.sh is executable."""
|
||||
script = PROJECT_ROOT / "scripts" / "stop.sh"
|
||||
if not script.exists():
|
||||
pytest.skip("stop.sh not found")
|
||||
assert os.access(script, os.X_OK), "stop.sh should be executable"
|
||||
|
||||
def test_promote_script_exists(self):
|
||||
"""Test that promote.sh script exists."""
|
||||
script = PROJECT_ROOT / "scripts" / "promote.sh"
|
||||
assert script.exists(), "scripts/promote.sh should exist"
|
||||
|
||||
def test_promote_script_executable(self):
|
||||
"""Test that promote.sh is executable."""
|
||||
script = PROJECT_ROOT / "scripts" / "promote.sh"
|
||||
if not script.exists():
|
||||
pytest.skip("promote.sh not found")
|
||||
assert os.access(script, os.X_OK), "promote.sh should be executable"
|
||||
|
||||
def test_status_script_exists(self):
|
||||
"""Test that status.sh script exists."""
|
||||
script = PROJECT_ROOT / "scripts" / "status.sh"
|
||||
assert script.exists(), "scripts/status.sh should exist"
|
||||
|
||||
def test_status_script_executable(self):
|
||||
"""Test that status.sh is executable."""
|
||||
script = PROJECT_ROOT / "scripts" / "status.sh"
|
||||
if not script.exists():
|
||||
pytest.skip("status.sh not found")
|
||||
assert os.access(script, os.X_OK), "status.sh should be executable"
|
||||
|
||||
|
||||
class TestGitIgnore:
|
||||
"""Test .gitignore configuration for environment files."""
|
||||
|
||||
def test_gitignore_exists(self):
|
||||
"""Test that .gitignore exists."""
|
||||
gitignore = PROJECT_ROOT / ".gitignore"
|
||||
assert gitignore.exists(), ".gitignore should exist"
|
||||
|
||||
def test_gitignore_excludes_env(self):
|
||||
"""Test that .gitignore excludes .env file."""
|
||||
gitignore = PROJECT_ROOT / ".gitignore"
|
||||
if not gitignore.exists():
|
||||
pytest.skip(".gitignore not found")
|
||||
|
||||
content = gitignore.read_text()
|
||||
# Check for .env exclusion pattern
|
||||
assert ".env" in content, ".gitignore should exclude .env"
|
||||
|
||||
def test_gitignore_includes_env_dev(self):
|
||||
"""Test that .gitignore includes .env.dev (not excluded)."""
|
||||
gitignore = PROJECT_ROOT / ".gitignore"
|
||||
if not gitignore.exists():
|
||||
pytest.skip(".gitignore not found")
|
||||
|
||||
content = gitignore.read_text()
|
||||
# Check for .env.dev inclusion pattern (negation)
|
||||
assert "!.env.dev" in content, ".gitignore should include .env.dev"
|
||||
|
||||
def test_gitignore_includes_env_staging(self):
|
||||
"""Test that .gitignore includes .env.staging (not excluded)."""
|
||||
gitignore = PROJECT_ROOT / ".gitignore"
|
||||
if not gitignore.exists():
|
||||
pytest.skip(".gitignore not found")
|
||||
|
||||
content = gitignore.read_text()
|
||||
# Check for .env.staging inclusion pattern (negation)
|
||||
assert "!.env.staging" in content, ".gitignore should include .env.staging"
|
||||
|
||||
|
||||
class TestDocumentation:
|
||||
"""Test that environment documentation exists."""
|
||||
|
||||
def test_environments_md_exists(self):
|
||||
"""Test that environments.md documentation exists."""
|
||||
doc = PROJECT_ROOT / "docs" / "architecture" / "environments.md"
|
||||
assert doc.exists(), "docs/architecture/environments.md should exist"
|
||||
|
||||
def test_environment_setup_guide_exists(self):
|
||||
"""Test that environment-setup.md guide exists."""
|
||||
doc = PROJECT_ROOT / "docs" / "guides" / "environment-setup.md"
|
||||
assert doc.exists(), "docs/guides/environment-setup.md should exist"
|
||||
|
||||
def test_test_environment_setup_script_exists(self):
|
||||
"""Test that test-environment-setup.sh script exists."""
|
||||
script = PROJECT_ROOT / "scripts" / "test-environment-setup.sh"
|
||||
assert script.exists(), "scripts/test-environment-setup.sh should exist"
|
||||
|
||||
|
||||
class TestEnvironmentIsolation:
|
||||
"""Test environment isolation settings."""
|
||||
|
||||
@pytest.fixture
|
||||
def env_configs(self) -> Dict[str, Dict[str, str]]:
|
||||
"""Parse environment files into dictionaries."""
|
||||
configs = {}
|
||||
for env_name in ["dev", "staging"]:
|
||||
env_file = PROJECT_ROOT / f".env.{env_name}"
|
||||
if not env_file.exists():
|
||||
continue
|
||||
config = {}
|
||||
for line in env_file.read_text().split("\n"):
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and "=" in line:
|
||||
key, value = line.split("=", 1)
|
||||
config[key.strip()] = value.strip()
|
||||
configs[env_name] = config
|
||||
return configs
|
||||
|
||||
def test_unique_compose_project_names(self, env_configs):
|
||||
"""Test that each environment has a unique COMPOSE_PROJECT_NAME."""
|
||||
if len(env_configs) < 2:
|
||||
pytest.skip("Need at least 2 environment files")
|
||||
|
||||
project_names = [
|
||||
config.get("COMPOSE_PROJECT_NAME")
|
||||
for config in env_configs.values()
|
||||
if config.get("COMPOSE_PROJECT_NAME")
|
||||
]
|
||||
assert len(project_names) == len(set(project_names)), "COMPOSE_PROJECT_NAME should be unique per environment"
|
||||
|
||||
def test_unique_database_names(self, env_configs):
|
||||
"""Test that each environment uses a unique database name."""
|
||||
if len(env_configs) < 2:
|
||||
pytest.skip("Need at least 2 environment files")
|
||||
|
||||
db_names = [
|
||||
config.get("POSTGRES_DB")
|
||||
for config in env_configs.values()
|
||||
if config.get("POSTGRES_DB")
|
||||
]
|
||||
assert len(db_names) == len(set(db_names)), "POSTGRES_DB should be unique per environment"
|
||||
|
||||
def test_debug_disabled_in_staging(self, env_configs):
|
||||
"""Test that DEBUG is disabled in staging environment."""
|
||||
if "staging" not in env_configs:
|
||||
pytest.skip(".env.staging not found")
|
||||
|
||||
debug_value = env_configs["staging"].get("DEBUG", "").lower()
|
||||
assert debug_value == "false", "DEBUG should be false in staging"
|
||||
|
||||
def test_debug_enabled_in_dev(self, env_configs):
|
||||
"""Test that DEBUG is enabled in dev environment."""
|
||||
if "dev" not in env_configs:
|
||||
pytest.skip(".env.dev not found")
|
||||
|
||||
debug_value = env_configs["dev"].get("DEBUG", "").lower()
|
||||
assert debug_value == "true", "DEBUG should be true in development"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user