// E2E DRAMATIC PROOF: kill Forgejo, the CMS still commits. // // Stops the forgejo service on p620, performs an admin web-edit, // asserts that the commit lands in our local bare repo, then brings // Forgejo back up. If Forgejo were on tdd.md's edit path this would // fail — the test passes only because c14_git owns the commit path // and Forgejo is genuinely unused for syntaxai/tdd.md. // // Run this only when you can tolerate a few seconds of Forgejo // downtime (agent kata pushes will fail during the test). It's a // separate spec file so you can opt in: bunx playwright test // e2e/git-native-forgejo-down.spec.ts. import { test, expect } from "@playwright/test"; import * as fs from "fs"; import * as path from "path"; import { execSync } from "child_process"; const SCREENSHOT_DIR = "test-results/git-native-forgejo-down"; // When run inside a Flatpak sandbox (e.g. the VS Code .flatpak), `ssh` lives // on the host so we have to hop through flatpak-spawn. When run on the host // directly, flatpak-spawn isn't on PATH — call ssh straight. Detect by env. const SSH_HOP = process.env.FLATPAK_ID ? "flatpak-spawn --host ssh" : "ssh"; const sshExec = (cmd: string): string => execSync(`${SSH_HOP} p620 '${cmd}'`, { encoding: "utf8" }).trim(); const sshGit = (args: string): string => sshExec(`git --git-dir=/home/scri/repos/tdd.md.git ${args}`); const forgejoIsUp = (): boolean => { try { // The container's HTTP API answers when Forgejo is up; returns // empty/error when stopped. We check the published port from the // host's perspective via SSH — same network path the tdd-md // container would use. const out = sshExec( "curl -s -o /dev/null -w '%{http_code}' --max-time 2 http://localhost:44400/api/v1/version || echo 000", ); return out === "200"; } catch { return false; } }; test.beforeAll(() => { fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); }); const ADMIN_AUTH_FILE = ".auth/admin.json"; test.describe("CMS works with Forgejo stopped", () => { // Opt-in via env: this test stops the live forgejo container briefly to // prove the CMS is git-native. Don't fire it from a casual `bun x // playwright test` — only when the operator is OK with a few seconds of // forgejo downtime. test.skip( process.env.RUN_DESTRUCTIVE_E2E !== "1", "set RUN_DESTRUCTIVE_E2E=1 to run — this stops the live forgejo container", ); test.skip(!fs.existsSync(ADMIN_AUTH_FILE), `no ${ADMIN_AUTH_FILE} found`); test.use({ storageState: ADMIN_AUTH_FILE }); // The teardown re-starts forgejo even if the test fails halfway. test.afterAll(async () => { try { sshExec("systemctl --user start forgejo.service 2>/dev/null || true"); sshExec("sleep 3"); } catch { // Best effort. } }); test("admin edit succeeds while forgejo container is stopped", async ({ page, request }) => { // Confirm forgejo is currently up before we start. expect(forgejoIsUp()).toBe(true); // STOP FORGEJO via systemd — the .service has Restart=always, so // `podman stop` would just trigger a restart. Stopping the unit // keeps it down for the duration of the test. sshExec("systemctl --user stop forgejo.service"); sshExec("sleep 2"); expect(forgejoIsUp()).toBe(false); try { const headBefore = sshGit("rev-parse refs/heads/main"); const before = await (await request.get("/content/sama/skill.md")).text(); const marker = ``; const newBody = before + "\n" + marker + "\n"; await page.goto("/edit/sama/skill"); const textarea = page.locator("textarea[name='body']"); await expect(textarea).toBeVisible(); await textarea.fill(newBody); await page.locator("button[type='submit']").click(); // The save must succeed despite Forgejo being down. If it // didn't, something in the edit path is still calling Forgejo // and we have not actually decoupled. await expect(page.getByRole("heading", { name: /applied live/i })).toBeVisible({ timeout: 8000 }); const commitLink = page.locator('a[href^="/GIT/syntaxai/tdd.md/commit/"]'); await expect(commitLink).toBeVisible(); const newSha = (await commitLink.getAttribute("href"))! .replace("/GIT/syntaxai/tdd.md/commit/", ""); await page.screenshot({ path: path.join(SCREENSHOT_DIR, "1-applied-live-while-forgejo-down.png"), fullPage: true, }); // Bare repo advanced. const headAfter = sshGit("rev-parse refs/heads/main"); expect(headAfter).toBe(newSha); expect(headAfter).not.toBe(headBefore); // /GIT/.../commit/ must also still render — read path is // also c14_git, no Forgejo involvement. const commitPage = await page.goto(`/GIT/syntaxai/tdd.md/commit/${newSha}`); expect(commitPage?.status()).toBe(200); await page.screenshot({ path: path.join(SCREENSHOT_DIR, "2-commit-view-while-forgejo-down.png"), fullPage: true, }); } finally { // Restart forgejo so agent kata pushes work again. sshExec("systemctl --user start forgejo.service"); } }); });