Contain all data under ./data/<env> with startup enforcement
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user