// E2E: every blog post in ALL_POSTS is reachable via /GIT/. // // Crawls the registry's slugs (lifted into a literal array here so // the test file doesn't import server-side modules) and asserts: // 1. /GIT/syntaxai/tdd.md/tree/main/content/blog lists each post // 2. /GIT/syntaxai/tdd.md/blob/main/content/blog/.md renders // the post (markdown rendered via marked into the chrome) // 3. /GIT/syntaxai/tdd.md/raw/main/content/blog/.md serves // the raw markdown // Plus the tree home (/GIT/syntaxai/tdd.md/tree/main) shows the // top-level directories (content/, src/, public/, scripts/, etc.). import { test, expect } from "@playwright/test"; import * as fs from "fs"; import * as path from "path"; // Mirror of c31_blog.ts ALL_POSTS slugs. If a post is added there, // add the slug here too. Kept inline to avoid pulling server code // into the test process. const BLOG_SLUGS = [ "sama-meets-git-cms", "from-rules-to-checks", "agentic-coding-corpus-three-patterns", "claude-code-harness-postmortem", "three-constraints-agentic-coding", "tweag-handbook-tdd", "aider-tdd", "cursor-tdd", "claude-code-tdd", ]; const SCREENSHOT_DIR = "test-results/git-content-browse"; test.beforeAll(() => { fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); }); test.describe("/GIT browses the local bare repo", () => { test("repo root tree lists the top-level directories", async ({ page }) => { const res = await page.goto("/GIT/syntaxai/tdd.md/tree/main"); expect(res?.status()).toBe(200); // Top-level dirs we expect after the dev tree was pushed. for (const dir of ["content", "src", "public", "scripts", "e2e"]) { await expect( page.locator(`a[href="/GIT/syntaxai/tdd.md/tree/main/${dir}"]`), ).toBeVisible(); } // Top-level files await expect( page.locator('a[href="/GIT/syntaxai/tdd.md/blob/main/package.json"]'), ).toBeVisible(); await page.screenshot({ path: path.join(SCREENSHOT_DIR, "1-repo-root-tree.png"), fullPage: true, }); }); test("content/blog tree lists every post in ALL_POSTS", async ({ page }) => { const res = await page.goto("/GIT/syntaxai/tdd.md/tree/main/content/blog"); expect(res?.status()).toBe(200); for (const slug of BLOG_SLUGS) { const link = page.locator( `a[href="/GIT/syntaxai/tdd.md/blob/main/content/blog/${slug}.md"]`, ); await expect(link, `link to ${slug}.md must be present`).toBeVisible(); } await page.screenshot({ path: path.join(SCREENSHOT_DIR, "2-content-blog-tree.png"), fullPage: true, }); }); for (const slug of BLOG_SLUGS) { test(`blob view renders ${slug}.md as markdown via /GIT`, async ({ page }) => { const res = await page.goto( `/GIT/syntaxai/tdd.md/blob/main/content/blog/${slug}.md`, ); expect(res?.status()).toBe(200); // The repo-blob-rendered container is what marked.parse output // lands in. It must exist + be non-empty. const rendered = page.locator(".repo-blob-rendered"); await expect(rendered).toBeVisible(); const text = (await rendered.textContent()) ?? ""; expect(text.length).toBeGreaterThan(200); // The breadcrumb must show the file path so users can climb. await expect(page.locator(".commit-breadcrumb")).toContainText(`${slug}.md`); }); test(`raw endpoint serves ${slug}.md as text/plain via /GIT`, async ({ request }) => { const res = await request.get( `/GIT/syntaxai/tdd.md/raw/main/content/blog/${slug}.md`, ); expect(res.status()).toBe(200); expect(res.headers()["content-type"]).toMatch(/text\/plain/); const body = await res.text(); // Frontmatter or first heading — every blog post has one. expect(body.length).toBeGreaterThan(200); }); } test("path traversal is rejected", async ({ request }) => { for (const evil of [ "/GIT/syntaxai/tdd.md/blob/main/../etc/passwd", "/GIT/syntaxai/tdd.md/blob/main/content/../../etc/passwd", "/GIT/syntaxai/tdd.md/tree/main//content", ]) { const res = await request.get(evil); expect(res.status(), `${evil} must 404`).toBe(404); } }); test("non-allowed (owner, repo) 404s — only syntaxai/tdd.md is served", async ({ request, }) => { const res = await request.get("/GIT/someone/random-repo/tree/main"); expect(res.status()).toBe(404); }); });