Archived
Preserve the GitHub history and the newer deployed source from ct_134_karldns. Exclude runtime data, credentials, AppleDouble files and local backup copies. Make Gitea the canonical source for documentation, package metadata and project links.
262 lines
8.3 KiB
Python
262 lines
8.3 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
from fastapi.testclient import TestClient
|
|
|
|
from karldns import DdnsSettings, make_app
|
|
|
|
ROOT = Path(__file__).parents[1]
|
|
|
|
|
|
def run_script(*args: str, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
|
|
merged_env = os.environ.copy()
|
|
if env:
|
|
merged_env.update(env)
|
|
return subprocess.run(
|
|
args,
|
|
check=True,
|
|
cwd=ROOT,
|
|
env=merged_env,
|
|
text=True,
|
|
capture_output=True,
|
|
)
|
|
|
|
|
|
def test_shell_scripts_parse() -> None:
|
|
for script in ("install-linux.sh", "karldns-update.sh", "uninstall-linux.sh"):
|
|
run_script("sh", "-n", str(ROOT / "scripts" / script))
|
|
|
|
|
|
def test_updater_strips_fritzbox_placeholders_and_caches_ip(tmp_path: Path) -> None:
|
|
app = make_app(
|
|
DdnsSettings(
|
|
database_path=tmp_path / "ddns.sqlite3",
|
|
admin_password="admin",
|
|
public_base_url="https://karldns.de",
|
|
hostname_suffix="karldns.de",
|
|
)
|
|
)
|
|
client = TestClient(app)
|
|
account = client.post("/api/v1/hostnames").json()
|
|
update_url = account["update_url"]
|
|
script = tmp_path / "curl"
|
|
log_file = tmp_path / "curl.log"
|
|
cache_file = tmp_path / "last-ip"
|
|
lock_file = tmp_path / "updater.lock"
|
|
|
|
script.write_text(
|
|
f"""#!/bin/sh
|
|
printf '%s\\n' "$*" >> {log_file}
|
|
case "$*" in
|
|
*'<ipaddr>'*|*'<ip6addr>'*) printf 'bad placeholder leaked\\n'; exit 1 ;;
|
|
*'myip=203.0.113.25'*) printf 'good 203.0.113.25\\n' ;;
|
|
*) printf 'bad request\\n'; exit 1 ;;
|
|
esac
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
script.chmod(0o700)
|
|
|
|
result = run_script(
|
|
"sh",
|
|
str(ROOT / "scripts" / "karldns-update.sh"),
|
|
env={
|
|
"KARLDNS_UPDATE_URL": update_url,
|
|
"KARLDNS_CURL": str(script),
|
|
"KARLDNS_CACHE_FILE": str(cache_file),
|
|
"KARLDNS_LOCK_FILE": str(lock_file),
|
|
"KARLDNS_IPV4": "203.0.113.25",
|
|
"KARLDNS_IPV6": "off",
|
|
},
|
|
)
|
|
|
|
assert "good 203.0.113.25" in result.stdout
|
|
assert cache_file.read_text(encoding="utf-8").strip() == "203.0.113.25|"
|
|
assert "<ipaddr>" not in log_file.read_text(encoding="utf-8")
|
|
|
|
|
|
def test_installer_registers_generated_hostname_before_use(tmp_path: Path) -> None:
|
|
bin_dir = tmp_path / "bin"
|
|
home_dir = tmp_path / "home"
|
|
log_file = tmp_path / "curl.log"
|
|
bin_dir.mkdir()
|
|
home_dir.mkdir()
|
|
curl = bin_dir / "curl"
|
|
curl.write_text(
|
|
f"""#!/bin/sh
|
|
printf '%s\\n' "$*" >> {log_file}
|
|
output_file=""
|
|
previous=""
|
|
for argument in "$@"; do
|
|
if [ "$previous" = "-o" ]; then
|
|
output_file="$argument"
|
|
fi
|
|
previous="$argument"
|
|
done
|
|
case "$*" in
|
|
*'/karldns-update.sh'*)
|
|
cat > "$output_file" <<'EOS'
|
|
#!/bin/sh
|
|
printf 'updater placeholder\\n'
|
|
EOS
|
|
;;
|
|
*'/api/v1/hostnames'*)
|
|
printf '%s\\n' '{{"hostname":"abc.karldns.test","status":"pending","update_url":"https://karldns.test/api/v1/router/update/karl_update_secret_12345678901234567890?myip=<ipaddr>&myipv6=<ip6addr>","dashboard_url":"https://karldns.test/m/karl_manage_secret_12345678901234567890","registration_url":"https://karldns.test/api/v1/router/register/karl_manage_secret_12345678901234567890"}}'
|
|
;;
|
|
*'/api/v1/router/register/karl_manage_secret_12345678901234567890'*)
|
|
printf '%s\\n' 'good registered'
|
|
;;
|
|
*)
|
|
printf 'unexpected curl call: %s\\n' "$*" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
curl.chmod(0o700)
|
|
|
|
result = run_script(
|
|
"sh",
|
|
str(ROOT / "scripts" / "install-linux.sh"),
|
|
"--mode",
|
|
"user",
|
|
"--no-cron",
|
|
"--no-run",
|
|
"--base-url",
|
|
"https://karldns.test",
|
|
env={
|
|
"HOME": str(home_dir),
|
|
"PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}",
|
|
"KARLDNS_BASE_URL": "https://karldns.test",
|
|
|
|
},
|
|
)
|
|
|
|
log = log_file.read_text(encoding="utf-8")
|
|
assert log.count("Content-Type: application/x-www-form-urlencoded") == 2
|
|
assert "https://karldns.test/karldns-update.sh" in log
|
|
assert "/api/v1/hostnames" in log
|
|
assert "/api/v1/router/register/karl_manage_secret_12345678901234567890" in log
|
|
assert "KarlDNS: registering hostname" in result.stdout
|
|
config = home_dir / ".config" / "karldns" / "updater.env"
|
|
config_text = config.read_text(encoding="utf-8")
|
|
assert (
|
|
"KARLDNS_UPDATE_URL='https://karldns.test/api/v1/router/update/karl_update_secret_12345678901234567890'"
|
|
in config_text
|
|
)
|
|
assert "KARLDNS_HOSTNAME='abc.karldns.test'" in config_text
|
|
|
|
|
|
def test_user_installer_and_uninstaller_manage_cron_idempotently(tmp_path: Path) -> None:
|
|
bin_dir = tmp_path / "bin"
|
|
home_dir = tmp_path / "home"
|
|
log_file = tmp_path / "curl.log"
|
|
crontab_file = tmp_path / "crontab.txt"
|
|
bin_dir.mkdir()
|
|
home_dir.mkdir()
|
|
curl = bin_dir / "curl"
|
|
crontab = bin_dir / "crontab"
|
|
curl.write_text(
|
|
f"""#!/bin/sh
|
|
printf '%s\\n' "$*" >> {log_file}
|
|
output_file=""
|
|
previous=""
|
|
for argument in "$@"; do
|
|
if [ "$previous" = "-o" ]; then
|
|
output_file="$argument"
|
|
fi
|
|
previous="$argument"
|
|
done
|
|
case "$*" in
|
|
*'/karldns-update.sh'*)
|
|
cat > "$output_file" <<'EOS'
|
|
#!/bin/sh
|
|
printf 'updater placeholder\\n'
|
|
EOS
|
|
;;
|
|
*'/api/v1/hostnames'*)
|
|
printf '%s\\n' '{{"hostname":"abc.karldns.test","status":"pending","update_url":"https://karldns.test/api/v1/router/update/karl_update_secret_12345678901234567890?myip=<ipaddr>&myipv6=<ip6addr>","dashboard_url":"https://karldns.test/m/karl_manage_secret_12345678901234567890","registration_url":"https://karldns.test/api/v1/router/register/karl_manage_secret_12345678901234567890"}}'
|
|
;;
|
|
*'/api/v1/router/register/karl_manage_secret_12345678901234567890'*)
|
|
printf '%s\\n' 'good registered'
|
|
;;
|
|
*)
|
|
printf 'unexpected curl call: %s\\n' "$*" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
crontab.write_text(
|
|
f"""#!/bin/sh
|
|
if [ "$1" = "-l" ]; then
|
|
cat {crontab_file} 2>/dev/null || exit 1
|
|
exit 0
|
|
fi
|
|
cp "$1" {crontab_file}
|
|
""",
|
|
encoding="utf-8",
|
|
)
|
|
curl.chmod(0o700)
|
|
crontab.chmod(0o700)
|
|
env = {
|
|
"HOME": str(home_dir),
|
|
"PATH": f"{bin_dir}{os.pathsep}{os.environ['PATH']}",
|
|
"KARLDNS_BASE_URL": "https://karldns.test",
|
|
"KARLDNS_RAW_BASE_URL": "https://raw.karldns.test",
|
|
}
|
|
|
|
for _ in range(2):
|
|
run_script(
|
|
"sh",
|
|
str(ROOT / "scripts" / "install-linux.sh"),
|
|
"--mode",
|
|
"user",
|
|
"--no-run",
|
|
env=env,
|
|
)
|
|
|
|
cron_text = crontab_file.read_text(encoding="utf-8")
|
|
log = log_file.read_text(encoding="utf-8")
|
|
assert cron_text.count("# karldns-update start") == 1
|
|
assert "KARLDNS_CONFIG=" in cron_text
|
|
assert "karldns-update" in cron_text
|
|
assert log.count("/api/v1/hostnames") == 1
|
|
assert log.count("/api/v1/router/register/karl_manage_secret_12345678901234567890") == 1
|
|
assert (home_dir / ".local" / "bin" / "karldns-update").exists()
|
|
|
|
run_script("sh", str(ROOT / "scripts" / "uninstall-linux.sh"), "--mode", "user", env=env)
|
|
|
|
assert "# karldns-update start" not in crontab_file.read_text(encoding="utf-8")
|
|
assert not (home_dir / ".local" / "bin" / "karldns-update").exists()
|
|
assert (home_dir / ".config" / "karldns" / "updater.env").exists()
|
|
|
|
run_script("sh", str(ROOT / "scripts" / "uninstall-linux.sh"), "--mode", "user", "--delete-config", env=env)
|
|
|
|
assert not (home_dir / ".config" / "karldns").exists()
|
|
assert not (home_dir / ".cache" / "karldns").exists()
|
|
|
|
|
|
def test_hosted_script_routes_serve_bundled_scripts(tmp_path: Path) -> None:
|
|
app = make_app(DdnsSettings(database_path=tmp_path / "ddns.sqlite3", admin_password="admin"))
|
|
client = TestClient(app)
|
|
|
|
assert client.get("/docs").text.count("Content-Type: application/x-www-form-urlencoded") >= 3
|
|
|
|
for path, target in {
|
|
"/install.sh": "install-linux.sh",
|
|
"/uninstall.sh": "uninstall-linux.sh",
|
|
"/karldns-update.sh": "karldns-update.sh",
|
|
}.items():
|
|
response = client.get(path, follow_redirects=False)
|
|
assert response.status_code == 200
|
|
assert response.content == (ROOT / "scripts" / target).read_bytes()
|
|
assert response.headers["content-type"].startswith("text/plain")
|
|
assert response.headers["cache-control"] == "no-cache"
|
|
assert "location" not in response.headers
|