- validate instance names at the host lib and web boundary against
[a-z0-9][a-z0-9_-]{0,63} to prevent path traversal via Server.name
- fail-closed on SECRET_KEY: load_config returns None when env unset,
create_app raises if missing or "dev" outside TESTING
- close login timing oracle by hashing a dummy digest when the user
is not found, equalizing response time
- set SESSION_COOKIE_SECURE outside TESTING
- delete_instance tolerates stop_service and fusermount3 failures so
partially-initialized instances clean up without contract breaks;
drops the is_mount() preflight that violated AGENTS.md
- document claim_next_job's single-process assumption
- clarify emit_step contract via docstring
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
75 lines
2.7 KiB
Python
75 lines
2.7 KiB
Python
import click
|
|
import pytest
|
|
|
|
from l4d2web.app import create_app
|
|
from l4d2web.config import load_config
|
|
|
|
|
|
def test_load_config_uses_environment(monkeypatch) -> None:
|
|
monkeypatch.setenv("DATABASE_URL", "sqlite:///env.db")
|
|
monkeypatch.setenv("SECRET_KEY", "env-secret")
|
|
monkeypatch.setenv("JOB_WORKER_THREADS", "2")
|
|
|
|
config = load_config()
|
|
|
|
assert config["DATABASE_URL"] == "sqlite:///env.db"
|
|
assert config["SECRET_KEY"] == "env-secret"
|
|
assert config["JOB_WORKER_THREADS"] == 2
|
|
|
|
|
|
def test_create_app_does_not_overwrite_database_url_env(tmp_path, monkeypatch) -> None:
|
|
monkeypatch.setenv("DATABASE_URL", "sqlite:///env.db")
|
|
db_url = f"sqlite:///{tmp_path/'app.db'}"
|
|
|
|
create_app({"TESTING": True, "DATABASE_URL": db_url, "SECRET_KEY": "test"})
|
|
|
|
assert load_config()["DATABASE_URL"] == "sqlite:///env.db"
|
|
|
|
|
|
def test_create_app_raises_when_secret_key_missing(tmp_path, monkeypatch) -> None:
|
|
db_url = f"sqlite:///{tmp_path/'nokey.db'}"
|
|
monkeypatch.delenv("SECRET_KEY", raising=False)
|
|
|
|
with pytest.raises(RuntimeError):
|
|
create_app({"TESTING": False, "DATABASE_URL": db_url})
|
|
|
|
|
|
def test_create_app_raises_when_secret_key_is_dev(tmp_path, monkeypatch) -> None:
|
|
db_url = f"sqlite:///{tmp_path/'devkey.db'}"
|
|
monkeypatch.setenv("SECRET_KEY", "dev")
|
|
|
|
with pytest.raises(RuntimeError):
|
|
create_app({"TESTING": False, "DATABASE_URL": db_url})
|
|
|
|
|
|
def test_session_cookie_secure_in_production(tmp_path, monkeypatch) -> None:
|
|
db_url = f"sqlite:///{tmp_path/'cookie.db'}"
|
|
monkeypatch.setenv("DATABASE_URL", db_url)
|
|
monkeypatch.setattr("l4d2web.app.recover_stale_jobs", lambda: None)
|
|
monkeypatch.setattr("l4d2web.app.start_job_workers", lambda app: None)
|
|
|
|
app = create_app({"TESTING": False, "DATABASE_URL": db_url, "SECRET_KEY": "real-secret"})
|
|
|
|
assert app.config["SESSION_COOKIE_SECURE"] is True
|
|
|
|
|
|
def test_session_cookie_secure_disabled_in_testing(tmp_path, monkeypatch) -> None:
|
|
db_url = f"sqlite:///{tmp_path/'cookie-test.db'}"
|
|
monkeypatch.setenv("DATABASE_URL", db_url)
|
|
|
|
app = create_app({"TESTING": True, "DATABASE_URL": db_url, "SECRET_KEY": "test"})
|
|
|
|
assert app.config["SESSION_COOKIE_SECURE"] is False
|
|
|
|
|
|
def test_create_app_skips_job_workers_in_cli_context(tmp_path, monkeypatch) -> None:
|
|
calls = []
|
|
db_url = f"sqlite:///{tmp_path/'cli.db'}"
|
|
monkeypatch.setenv("DATABASE_URL", db_url)
|
|
monkeypatch.setattr("l4d2web.app.recover_stale_jobs", lambda: calls.append("recover"))
|
|
monkeypatch.setattr("l4d2web.app.start_job_workers", lambda app: calls.append("start"))
|
|
|
|
with click.Context(click.Command("flask")):
|
|
create_app({"TESTING": False, "DATABASE_URL": db_url, "SECRET_KEY": "test"})
|
|
|
|
assert calls == []
|