90 lines
5.0 KiB
Python
90 lines
5.0 KiB
Python
"""Real Chromium DOM + real ASGI backend, bridged in-process (no network deployment test).
|
|
|
|
Requires playwright. Browser cookie/CSRF behavior is covered separately in test_api.py.
|
|
The bridge uses TestClient's cookie jar and supplies the configured same-origin header.
|
|
"""
|
|
import os
|
|
from pathlib import Path
|
|
import secrets
|
|
import sys
|
|
import tempfile
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "backend" / "src"))
|
|
from fastapi.testclient import TestClient
|
|
from playwright.sync_api import sync_playwright
|
|
from apc.api import Settings, create_app
|
|
|
|
|
|
def main():
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
token = secrets.token_urlsafe(32)
|
|
app = create_app(Settings(token, str(Path(directory) / "smoke.db")))
|
|
with TestClient(app) as client, sync_playwright() as pw:
|
|
browser = pw.chromium.launch(executable_path=os.environ.get("CHROMIUM_PATH") or None,
|
|
args=["--no-sandbox"])
|
|
page = browser.new_page(viewport={"width": 1280, "height": 900})
|
|
errors = []
|
|
page.on("pageerror", lambda error: errors.append(str(error)))
|
|
def request(path, options):
|
|
response = client.request(options.get("method", "GET"), path,
|
|
content=options.get("body"), headers={"Content-Type": "application/json",
|
|
"Origin": "http://localhost:8000"})
|
|
return {"status": response.status_code, "body": response.text}
|
|
page.expose_function("asgiRequest", request)
|
|
html = client.get("/").text.replace('<script defer src="/static/app.js"></script>', "")
|
|
html = html.replace('<link rel="stylesheet" href="/static/style.css">', "")
|
|
page.set_content(html)
|
|
page.add_style_tag(content=client.get("/static/style.css").text)
|
|
page.evaluate("""() => { window.fetch = async (path, options = {}) => {
|
|
const response = await window.asgiRequest(path, options);
|
|
return new Response(response.body, {status: response.status,
|
|
headers: {'Content-Type': 'application/json'}});
|
|
}; }""")
|
|
page.add_script_tag(content=client.get("/static/app.js").text)
|
|
page.get_by_label("Administration token").fill(token)
|
|
page.get_by_role("button", name="Sign in", exact=True).click()
|
|
page.locator("#dashboard").wait_for(state="visible")
|
|
for name in ("Example A", "Example B"):
|
|
page.get_by_label("Display name", exact=True).fill(name)
|
|
page.get_by_role("button", name="Add child", exact=True).click()
|
|
page.get_by_role("heading", name=name, exact=True).wait_for()
|
|
page.locator("#policy [name=daily_minutes]").fill("40")
|
|
page.get_by_role("button", name="Save shared policy").click()
|
|
page.get_by_text("Shared policy saved.", exact=False).wait_for()
|
|
assert page.locator(".child").count() == 2
|
|
first = page.locator(".child").first
|
|
first.get_by_text("Individual exceptions", exact=True).click()
|
|
first.locator("[name=daily_minutes]").fill("45")
|
|
first.get_by_role("button", name="Save exceptions").click()
|
|
page.get_by_text("Exceptions saved.", exact=False).wait_for()
|
|
first = page.locator(".child").first
|
|
assert "45 daily minutes" in first.inner_text()
|
|
first.get_by_role("button", name="Pair phone").click()
|
|
page.locator("#code-dialog").wait_for(state="visible")
|
|
code = page.locator("#issued-code").inner_text()
|
|
paired = client.post("/v1/pair", json={"code": code, "name": "Smoke test"}).json()
|
|
headers = {"Authorization": f"Bearer {paired['token']}"}
|
|
challenge = client.post("/v1/device/challenges", headers=headers).json()
|
|
answers = []
|
|
for q in challenge["questions"]:
|
|
a, op, b = q["prompt"].split()
|
|
answers.append({"question_id": q["id"], "answer": int(a) + int(b) if op == "+" else int(a) - int(b)})
|
|
result = client.post(f"/v1/device/challenges/{challenge['id']}/submit",
|
|
headers=headers, json={"answers": answers})
|
|
assert result.json()["awarded_minutes"] == 10
|
|
page.get_by_role("button", name="Close", exact=True).click()
|
|
page.get_by_role("button", name="Refresh status").click()
|
|
page.get_by_text("45 daily minutes + 10 earned today", exact=False).wait_for()
|
|
if os.environ.get("APC_SCREENSHOT"):
|
|
page.screenshot(path=os.environ["APC_SCREENSHOT"], full_page=True)
|
|
page.get_by_role("button", name="Sign out").click()
|
|
page.locator("#login-panel").wait_for(state="visible")
|
|
assert not errors, errors
|
|
browser.close()
|
|
print("Browser/ASGI smoke passed: login, two children, policy, override, pairing, reward, logout")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|