From 032f314eff11453467d593b51abfb723861455ec Mon Sep 17 00:00:00 2001 From: autonomic-bot Date: Wed, 27 May 2026 06:48:39 +0100 Subject: [PATCH] =?UTF-8?q?M6.5:=20enroll=20n8n=20(recipe=20#6,=20workflow?= =?UTF-8?q?=20automation)=20=E2=80=94=20tests=20authored=20(single-service?= =?UTF-8?q?,=20.n8n=20volume)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/n8n/recipe_meta.py | 7 +++++++ tests/n8n/test_backup.py | 29 +++++++++++++++++++++++++++ tests/n8n/test_install.py | 29 +++++++++++++++++++++++++++ tests/n8n/test_upgrade.py | 41 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+) create mode 100644 tests/n8n/recipe_meta.py create mode 100644 tests/n8n/test_backup.py create mode 100644 tests/n8n/test_install.py create mode 100644 tests/n8n/test_upgrade.py diff --git a/tests/n8n/recipe_meta.py b/tests/n8n/recipe_meta.py new file mode 100644 index 0000000..8547c43 --- /dev/null +++ b/tests/n8n/recipe_meta.py @@ -0,0 +1,7 @@ +# Per-recipe harness config for n8n (recipe #6 — workflow automation; single-service, stateful via +# the /home/node/.n8n volume, sqlite by default). Normal terminate-at-Traefik (no passthrough). +# n8n exposes /healthz (200 {"status":"ok"}) once up. +HEALTH_PATH = "/healthz" +HEALTH_OK = (200,) +DEPLOY_TIMEOUT = 600 +HTTP_TIMEOUT = 300 diff --git a/tests/n8n/test_backup.py b/tests/n8n/test_backup.py new file mode 100644 index 0000000..ab64a98 --- /dev/null +++ b/tests/n8n/test_backup.py @@ -0,0 +1,29 @@ +"""n8n — backup/restore stage (D2): write a marker into the backed-up /home/node/.n8n path, backup, +mutate, restore, assert the restored state matches the pre-mutation state. + +The n8n `app` service is labelled `backupbot.backup=true` with `backupbot.backup.path=/home/node/.n8n`, +so a marker file there is backed up; checked via `exec_in_app`.""" +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) +from harness import lifecycle # noqa: E402 + +MARKER = "/home/node/.n8n/ci-marker.txt" + + +def test_backup_mutate_restore(deployed, meta): + domain = deployed + + lifecycle.exec_in_app(domain, ["sh", "-c", f"echo original > {MARKER}"]) + assert lifecycle.exec_in_app(domain, ["cat", MARKER]).strip() == "original" + lifecycle.backup_app(domain) + + lifecycle.exec_in_app(domain, ["sh", "-c", f"echo mutated > {MARKER}"]) + assert lifecycle.exec_in_app(domain, ["cat", MARKER]).strip() == "mutated" + + 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 lifecycle.exec_in_app(domain, ["cat", MARKER]).strip() == "original", \ + "restore did not return the pre-mutation state" diff --git a/tests/n8n/test_install.py b/tests/n8n/test_install.py new file mode 100644 index 0000000..a8cc1d1 --- /dev/null +++ b/tests/n8n/test_install.py @@ -0,0 +1,29 @@ +"""n8n — install stage (recipe #6, workflow automation). D2 install + D3 Playwright.""" +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner")) +from harness import lifecycle # noqa: E402 + + +def test_healthz(deployed_app): + status = lifecycle.http_get(deployed_app, "/healthz") + assert status == 200, f"expected 200 from {deployed_app}/healthz, got {status}" + + +def test_playwright_loads_editor(deployed_app): + """A real browser loads the live n8n editor SPA 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, 304), f"page status {resp and resp.status}" + body = page.content().lower() + assert "n8n" in body or " {MARKER}"]) + assert lifecycle.exec_in_app(domain, ["cat", MARKER]).strip() == "upgrade-survives" + + lifecycle.upgrade_app(domain, version=os.environ.get("VERSION") or None) + 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 lifecycle.http_get(domain, meta["HEALTH_PATH"]) == 200 + assert lifecycle.exec_in_app(domain, ["cat", MARKER]).strip() == "upgrade-survives", \ + "data did not survive the upgrade"