diff --git a/tests/libredesk/custom/test_health.py b/tests/libredesk/custom/test_health.py new file mode 100644 index 0000000..675f29d --- /dev/null +++ b/tests/libredesk/custom/test_health.py @@ -0,0 +1,20 @@ +"""libredesk — health check: the app's /health endpoint responds 200 through Traefik. + +This is the same endpoint the compose healthcheck hits internally (:9000/health); exercising it via +the public domain confirms the app is bound and the proxy route is wired. +""" + +from __future__ import annotations + +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "runner")) +from harness import http as harness_http # noqa: E402 + + +def test_libredesk_health(live_app): + """GET /health → 200.""" + url = f"https://{live_app}/health" + status, _ = harness_http.retry_http_get(url, expect_status=(200,), max_wait=120, interval=5) + assert status == 200, f"GET {url} HTTP {status} (expected 200)" diff --git a/tests/libredesk/custom/test_ui.py b/tests/libredesk/custom/test_ui.py new file mode 100644 index 0000000..7848698 --- /dev/null +++ b/tests/libredesk/custom/test_ui.py @@ -0,0 +1,49 @@ +"""libredesk — UI probe: the served root HTML is the real LibreDesk app, not a fallback page. + +/health passing only proves the process is up. This asserts the front-end SPA is actually bound and +serving its own shell — a wedged backend or a misrouted proxy would 200 with generic/empty content. +""" + +from __future__ import annotations + +import os +import ssl +import sys +import urllib.request + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "..", "runner")) +from harness import http as harness_http # noqa: E402 + +_CTX = ssl.create_default_context() +_CTX.check_hostname = False +_CTX.verify_mode = ssl.CERT_NONE + + +def _get_body(url: str) -> tuple[int, str]: + req = urllib.request.Request(url, method="GET") + with urllib.request.urlopen(req, timeout=15, context=_CTX) as r: + return r.status, r.read().decode(errors="replace") + + +def test_libredesk_serves_app_shell(live_app): + """GET /; assert LibreDesk-specific brand or SPA-asset markers in the served HTML.""" + url = f"https://{live_app}/" + + def _ready(): + try: + status, body = _get_body(url) + except Exception: # noqa: BLE001 + return None + return body if status in (200, 301, 302) else None + + body = harness_http.assert_converges(_ready, f"GET {url}", max_wait=120, interval=5) + lower = body.lower() + + # Brand markers: the app title / meta / bundle references carry "libredesk". + brand = [m for m in ("libredesk", "libre desk") if m in lower] + # SPA asset markers: the built front-end serves hashed JS/CSS bundles + a favicon. + assets = [m for m in ("/assets/", "favicon", ".js", ".css", "
and repoint SECRET__VERSION at the bumped version. +insert_secret() { + local secret="$1" env_var="$2" value="$3" + local cur new num + cur=$(grep -E "^\s*${env_var}=" "$ENV_PATH" | tail -1 | cut -d= -f2 | tr -d '"\r' || echo "v1") + cur=${cur:-v1} + num=$(( ${cur#v} + 1 )); new="v${num}" + # abra forbids overwriting an existing version; insert at the fresh version. -C creates it, -o + # allows overwrite if a stale one exists, --no-input for non-interactive. + local log + log=$(abra app secret insert "$CCCI_APP_DOMAIN" "$secret" "$new" "$value" --no-input -C -o 2>&1) || + log=$(script -qec "abra app secret insert $CCCI_APP_DOMAIN $secret $new $value --no-input -C -o" /dev/null 2>&1) || + { echo " install_steps: abra app secret insert ${secret}@${new} failed: $log"; exit 1; } + sed -i "s|^\s*${env_var}=.*|${env_var}=${new}|" "$ENV_PATH" + echo " install_steps: ${secret} inserted at ${new} (was ${cur})" +} + +# 32 hex chars = 16 bytes (AES-256 key LibreDesk expects for encryption_key). +ENC_KEY=$(openssl rand -hex 16) + +# 10–72 chars with upper+lower+number+special. Build deterministically-compliant: a fixed +# upper+lower+digit+special prefix plus random hex entropy (lowercase letters + digits). +ADMIN_PWD="Ci$(openssl rand -hex 12)Aa1!" + +insert_secret enc_key SECRET_ENC_KEY_VERSION "$ENC_KEY" +insert_secret admin_pwd SECRET_ADMIN_PWD_VERSION "$ADMIN_PWD" + +echo " libredesk install_steps: enc_key (32-hex) + admin_pwd (complex) inserted; deploy will use them" diff --git a/tests/libredesk/recipe_meta.py b/tests/libredesk/recipe_meta.py new file mode 100644 index 0000000..730931f --- /dev/null +++ b/tests/libredesk/recipe_meta.py @@ -0,0 +1,32 @@ +# Per-recipe harness config for libredesk (LibreDesk — open-source, self-hosted customer-support / +# help desk; recipe repo recipe-maintainers/Libre-Desk, abra TYPE=libredesk). +# +# Stack (compose.yml): app (libredesk/libredesk:v2.4.0, serves on :9000 behind Traefik) + db +# (postgres:17-alpine, user/db=libredesk) + redis (redis:7-alpine). abra-entrypoint.sh self-installs +# the schema and runs migrations on boot (`--install --idempotent-install` then `--upgrade`), so the +# stack comes up ready with no post-deploy step. +# +# Secrets have STRICT formats that abra's generic generator does not satisfy, so install_steps.sh +# inserts compliant values before the single deploy (see that file): +# - enc_key must be 32 hex chars (AES-256 key) → openssl rand -hex 16 +# - admin_pwd must be 10–72 chars, upper+lower+num+sym → the "System" user's password +# - db_password is abra-generated (no special format) and left as-is. + +HEALTH_PATH = "/health" # the app's own healthcheck endpoint (compose hits :9000/health) +HEALTH_OK = (200,) +DEPLOY_TIMEOUT = 600 # app + postgres + redis; app runs install+migrate on first boot +HTTP_TIMEOUT = 300 +BACKUP_CAPABLE = True # db carries backupbot.backup labels (pg_dump pre-hook / psql restore-hook) + +# Only one published version exists so far (0.1.0+v2.4.0), so there is no PRIOR deployable base to +# upgrade FROM — the upgrade rung is intentionally N/A (a declared skip, never promoted to a pass). +# Drop this once a second version is published; the dynamic base then resolves it (last-green warm +# canonical → same-version step-back → main tip). +EXPECTED_NA = { + "upgrade": "only one published version so far (0.1.0+v2.4.0) — no prior deployable base to " + "upgrade FROM yet; drop this when a second version ships", +} + +# canon §2.B: enroll as a DATA-WARM canonical (all recipes enrolled — operator 2026-06-17). +# The weekly sweep promotes this recipe's canonical to its latest green RELEASE TAG. +WARM_CANONICAL = True