Files
dsh/server/tests/test_apk.py
T

135 lines
4.4 KiB
Python

"""APK distribution: /apk page, /apk/info, the artifact, and its QR.
These are deliberately unauthenticated — a new member opens /apk before holding
a token, exactly like /app.
"""
from __future__ import annotations
import json
def test_apk_page_is_public(client):
r = client.get("/apk")
assert r.status_code == 200, r.text
assert "text/html" in r.headers["content-type"]
body = r.text
assert "/apk/dsh-sync.apk" in body
assert "/apk/info" in body
assert "bash android/build.sh" in body
def test_apk_page_linked_from_app_shell(client):
r = client.get("/app")
assert 'href="/apk"' in r.text
def test_apk_info_reports_unavailable_without_build(client, monkeypatch):
"""An unbuilt server must say so instead of 500ing or offering a dead link."""
from dsh_sync import storage
from dsh_sync.main import create_app
monkeypatch.setattr(storage, "apk_path", lambda cfg: cfg.data_dir / "dist" / "missing.apk")
import dsh_sync.routers.pages as pages
monkeypatch.setattr(pages, "apk_path", lambda cfg: cfg.data_dir / "dist" / "missing.apk")
r = client.get("/apk/info")
assert r.status_code == 200
data = r.json()
assert data["available"] is False
assert "build.sh" in data["build_hint"]
def test_apk_download_404_when_not_built(client, monkeypatch):
import dsh_sync.routers.pages as pages
monkeypatch.setattr(pages, "apk_path", lambda cfg: cfg.data_dir / "dist" / "missing.apk")
r = client.get("/apk/dsh-sync.apk")
assert r.status_code == 404
assert "not been built" in r.json()["detail"]
def test_apk_download_serves_artifact(client, monkeypatch):
import dsh_sync.routers.pages as pages
payload = b"PK\x03\x04fake-apk-bytes"
real_path = pages.apk_path(client.app.state.cfg)
real_path.parent.mkdir(parents=True, exist_ok=True)
real_path.write_bytes(payload)
monkeypatch.setattr(pages, "apk_meta_path", lambda cfg: cfg.data_dir / "dist" / "nope.json")
r = client.get("/apk/dsh-sync.apk")
assert r.status_code == 200
assert r.content == payload
assert r.headers["content-type"] == "application/vnd.android.package-archive"
assert "dsh-sync.apk" in r.headers["content-disposition"]
def test_apk_info_merges_sidecar_metadata(client, monkeypatch):
import dsh_sync.routers.pages as pages
apk = pages.apk_path(client.app.state.cfg)
apk.parent.mkdir(parents=True, exist_ok=True)
apk.write_bytes(b"x" * 32)
pages.apk_meta_path(client.app.state.cfg).write_text(
json.dumps({"version": "9.9.9", "sha256": "deadbeef", "application_id": "com.dsh.sync"}),
encoding="utf-8",
)
data = client.get("/apk/info").json()
assert data["available"] is True
assert data["size_bytes"] == 32
assert data["version"] == "9.9.9"
assert data["sha256"] == "deadbeef"
def test_apk_info_survives_corrupt_sidecar(client):
"""A broken sidecar must not take the download down with it."""
import dsh_sync.routers.pages as pages
apk = pages.apk_path(client.app.state.cfg)
apk.parent.mkdir(parents=True, exist_ok=True)
apk.write_bytes(b"y" * 16)
pages.apk_meta_path(client.app.state.cfg).write_text("{not json", encoding="utf-8")
data = client.get("/apk/info").json()
assert data["available"] is True
assert data["size_bytes"] == 16
assert "version" not in data
def test_apk_qr_is_svg(client):
r = client.get("/apk/qr")
assert r.status_code == 200
assert r.headers["content-type"].startswith("image/svg+xml")
assert "<svg" in r.text
def test_apk_qr_host_override(client):
r = client.get("/apk/qr", params={"host": "192.168.9.9"})
assert r.status_code == 200
assert r.headers["content-type"].startswith("image/svg+xml")
def test_apk_qr_encodes_the_apk_page(client):
"""Decode-back: rebuild the QR for the expected URL and compare bytes."""
import io
import segno
r = client.get("/apk/qr", params={"host": "192.168.5.2"})
base = "192.168.5.2"
buf = io.BytesIO()
segno.make(f"http://{base}/apk", error="m").save(
buf, kind="svg", scale=8, border=2, dark="#000000", light="#ffffff"
)
assert r.content == buf.getvalue()
def test_favicon_redirects_to_icon(client):
"""Browsers request /favicon.ico unprompted; it must not 404."""
r = client.get("/favicon.ico", follow_redirects=False)
assert r.status_code == 307
assert r.headers["location"] == "/icon.svg"
assert client.get("/favicon.ico").status_code == 200