diff --git a/tests/lasuite-docs/recipe_meta.py b/tests/lasuite-docs/recipe_meta.py new file mode 100644 index 0000000..a688de3 --- /dev/null +++ b/tests/lasuite-docs/recipe_meta.py @@ -0,0 +1,17 @@ +# Per-recipe harness config for lasuite-docs (recipe #5 — multi-service + object-storage/S3). +# Stack: app(frontend) + backend(Django/impress) + celery + y-provider + docspec + db(postgres) + +# redis + minio(S3) + web(nginx). OIDC settings are config-only (validated by `manage.py check`, not +# fetched at boot), so the stack starts healthy with placeholder OIDC; login isn't exercised in CI. +# Many services -> generous timeouts. +HEALTH_PATH = "/" +HEALTH_OK = (200, 301, 302) +DEPLOY_TIMEOUT = 900 +HTTP_TIMEOUT = 600 + + +def EXTRA_ENV(domain): + # abra's internal per-deploy convergence timeout (the recipe's TIMEOUT env, default 300s) is too + # short for this 9-service stack on a COLD image cache (~9 large images: impress frontend/backend, + # minio, postgres18, redis, docspec, y-provider). Cold pulls exceed 300s -> "deploy timed out 🟠". + # Bump it so the harness deploy waits long enough; verified the stack converges 9/9 once pulled. + return {"TIMEOUT": "900"} diff --git a/tests/lasuite-docs/test_backup.py b/tests/lasuite-docs/test_backup.py new file mode 100644 index 0000000..9311122 --- /dev/null +++ b/tests/lasuite-docs/test_backup.py @@ -0,0 +1,33 @@ +"""lasuite-docs — backup/restore stage (D2): write a postgres marker, backup (pg_backup.sh pre-hook +dumps the DB), mutate (drop it), restore (post-hook reloads), assert the restored DB matches. + +Exercises the recipe's real DB-dump backup hook (postgres + minio are both backupbot-labelled); the +postgres marker is the meaningful Docs-metadata data path.""" +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) +from harness import lifecycle # noqa: E402 + + +def _psql(domain, sql): + cmd = f'PGPASSWORD=$(cat /run/secrets/postgres_p) psql -U docs -d docs -tAc "{sql}"' + return lifecycle.exec_in_app(domain, ["sh", "-c", cmd], service="db").strip() + + +def test_backup_mutate_restore(deployed, meta): + domain = deployed + + _psql(domain, "CREATE TABLE IF NOT EXISTS ci_marker(v text); DELETE FROM ci_marker; " + "INSERT INTO ci_marker VALUES('original');") + assert _psql(domain, "SELECT v FROM ci_marker;") == "original" + lifecycle.backup_app(domain) + + _psql(domain, "DROP TABLE ci_marker;") + assert _psql(domain, "SELECT to_regclass('public.ci_marker');") in ("", "NULL"), "drop did not take" + + lifecycle.restore_app(domain) + lifecycle.wait_healthy(domain, ok_codes=tuple(meta["HEALTH_OK"]), path=meta["HEALTH_PATH"], + deploy_timeout=meta["DEPLOY_TIMEOUT"], http_timeout=meta["HTTP_TIMEOUT"]) + assert _psql(domain, "SELECT v FROM ci_marker;") == "original", \ + "restore did not return the pre-mutation postgres state" diff --git a/tests/lasuite-docs/test_install.py b/tests/lasuite-docs/test_install.py new file mode 100644 index 0000000..f0c34b1 --- /dev/null +++ b/tests/lasuite-docs/test_install.py @@ -0,0 +1,33 @@ +"""lasuite-docs — install stage (recipe #5, multi-service + object-storage/S3). D2 install: the +multi-service stack (frontend + Django backend + celery + y-provider + docspec + postgres + redis + +minio + nginx) converges and serves the app over real HTTPS through the gateway. + +Login is OIDC-gated (no live OIDC provider in CI), so the functional assertion is that the frontend +SPA is served (unauthenticated landing), not an authenticated flow.""" +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) +from harness import lifecycle # noqa: E402 + + +def test_http_reachable(deployed_app): + status = lifecycle.http_get(deployed_app, "/") + assert status in (200, 301, 302), f"expected 2xx/3xx from {deployed_app}, got {status}" + + +def test_playwright_loads_frontend(deployed_app): + """A real browser loads the live Docs frontend (the SPA shell) over HTTPS.""" + from playwright.sync_api import sync_playwright + + url = f"https://{deployed_app}/" + with sync_playwright() as p: + browser = p.chromium.launch(args=["--no-sandbox"]) + try: + ctx = browser.new_context(ignore_https_errors=True) + page = ctx.new_page() + resp = page.goto(url, wait_until="domcontentloaded", timeout=60000) + assert resp is not None and resp.status in (200, 301, 302), f"page status {resp and resp.status}" + assert "