Contain all data under ./data/<env> with startup enforcement

This commit is contained in:
2026-10-05 14:46:36 +02:00
parent 4268b1ec9e
commit 44d0c08698
12 changed files with 156 additions and 28 deletions
+3 -2
View File
@@ -3,14 +3,15 @@ import os
import shutil
import subprocess
import sys
import tempfile
import time
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
TEST_DATA_DIR = Path(tempfile.mkdtemp(prefix="molodetz-test-"))
TEST_DATA_DIR = REPO_ROOT / "data" / f".tmp-pytest-{os.getpid()}"
shutil.rmtree(TEST_DATA_DIR, ignore_errors=True)
TEST_DATA_DIR.mkdir(parents=True, exist_ok=True)
TEST_PORT = int(os.environ.get("MOLODETZ_TEST_PORT", "8099"))
ADMIN_USERNAME = "retoor"
ADMIN_PASSWORD = "test-admin-pass-4f9c2a"
+106
View File
@@ -0,0 +1,106 @@
# retoor <retoor@molodetz.nl>
import os
import re
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
EXPECTED_KEYS = [
"SECRET_KEY",
"PLAYWRIGHT_HEADLESS",
"PLAYWRIGHT_SLOW_MO",
"MOLODETZ_ADMIN_USERNAME",
"MOLODETZ_ADMIN_EMAIL",
"MOLODETZ_ADMIN_PASSWORD",
"MOLODETZ_PORT",
"MOLODETZ_TEST_PORT",
"MOLODETZ_SITE_URL",
"MOLODETZ_DATA_DIR",
"MOLODETZ_DATABASE_URL",
"MOLODETZ_INTERNAL_BASE_URL",
"MOLODETZ_DISABLE_SERVICES",
"MOLODETZ_DISABLE_RATE_LIMIT",
"MOLODETZ_RATE_LIMIT",
"MOLODETZ_WEB_WORKERS",
"MOLODETZ_TEMPLATE_AUTO_RELOAD",
"MOLODETZ_STATIC_VERSION",
"MOLODETZ_LOG_LEVEL",
"MOLODETZ_PRESENCE_TIMEOUT_SECONDS",
"MOLODETZ_PRESENCE_ONLINE_LIMIT",
"MOLODETZ_PRESENCE_TRACK_LIMIT",
"MOLODETZ_PRESENCE_ONLINE_MARGIN_SECONDS",
"MOLODETZ_OUTBOUND_PROXY_URL",
"MOLODETZ_RCLONE_BIN",
"MOLODETZ_RCLONE_CONFIG",
"MOLODETZ_BACKUP_OFFLOAD_REMOTE",
"MOLODETZ_SITEMAP_TTL",
"MOLODETZ_LANDING_TTL",
"MOLODETZ_UNREAD_TTL",
]
NONEMPTY_PATH_KEYS = [
"MOLODETZ_DATA_DIR",
"MOLODETZ_DATABASE_URL",
"MOLODETZ_INTERNAL_BASE_URL",
"MOLODETZ_RCLONE_BIN",
"MOLODETZ_RCLONE_CONFIG",
]
def read_example():
values = {}
for line in (ROOT / ".env.example").read_text().splitlines():
match = re.match(r"^([A-Z][A-Z0-9_]*)=(.*)$", line)
if match:
values[match.group(1)] = match.group(2)
return values
def test_env_example_lists_every_key():
values = read_example()
assert sorted(values) == sorted(EXPECTED_KEYS)
def test_env_example_keeps_path_values_nonempty():
values = read_example()
for key in NONEMPTY_PATH_KEYS:
assert values[key].strip() != ""
def test_config_reads_no_unknown_keys():
source = (ROOT / "molodetz" / "config.py").read_text()
prefixed = set(re.findall(r'_env(?:_int)?\("([A-Z_]+)"', source))
assert set(re.findall(r'os\.environ\.get\("([A-Z_]+)"', source)) <= {"SECRET_KEY", "PLAYWRIGHT_HEADLESS", "PLAYWRIGHT_SLOW_MO"}
assert {f"MOLODETZ_{name}" for name in prefixed} <= set(EXPECTED_KEYS)
def run_config_import(data_dir, database_url):
env = {**os.environ, "MOLODETZ_DATA_DIR": data_dir, "MOLODETZ_DATABASE_URL": database_url}
return subprocess.run(
[sys.executable, "-c", "from molodetz import config"],
cwd=ROOT,
env=env,
capture_output=True,
text=True,
)
def test_data_dir_outside_root_refused():
result = run_config_import("/tmp/evil", "sqlite:////tmp/evil/molodetz.db")
assert result.returncode != 0
assert "outside" in result.stderr
def test_data_dir_inside_root_allowed():
target = ROOT / "data" / ".tmp-guard-probe"
result = run_config_import(str(target), f"sqlite:///{target / 'molodetz.db'}")
assert result.returncode == 0
def test_database_url_outside_data_dir_refused():
target = ROOT / "data" / ".tmp-guard-probe"
result = run_config_import(str(target), "sqlite:////tmp/evil/molodetz.db")
assert result.returncode != 0
assert "outside" in result.stderr