From 60cb759f4e507686702a5fcab08adf04699e0d13 Mon Sep 17 00:00:00 2001 From: Lilith Date: Sat, 14 Feb 2026 01:33:12 -0800 Subject: [PATCH] =?UTF-8?q?chore(tests):=20=F0=9F=94=A7=20Update=20test=20?= =?UTF-8?q?file=20blog.spec.ts=20to=20reflect=20latest=20expected=20behavi?= =?UTF-8?q?or?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- e2e/prod-auth/tests/blog.spec.ts | 183 +++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 e2e/prod-auth/tests/blog.spec.ts diff --git a/e2e/prod-auth/tests/blog.spec.ts b/e2e/prod-auth/tests/blog.spec.ts new file mode 100644 index 000000000..be859e006 --- /dev/null +++ b/e2e/prod-auth/tests/blog.spec.ts @@ -0,0 +1,183 @@ +/** + * Blog E2E Integration Tests + * + * Verifies blog functionality on atlilith.www: + * - Blog index page renders with posts + * - Individual blog post pages load + * - Blog API responds with correct data + * - Widget-based posts render their custom components + * - RSS feed is accessible + */ + +import { test, expect } from '@playwright/test'; + +const BASE_URL = process.env.BASE_URL || 'http://www.atlilith.e2e.local'; +const BLOG_API_URL = process.env.BLOG_API_URL || 'http://blog-api:3021'; + +test.describe('Blog Integration', () => { + test.describe('Blog API', () => { + test('GET /api/blog/posts returns published posts for atlilith.com', async ({ + request, + }) => { + const response = await request.get( + `${BASE_URL}/api/blog/posts?domain=atlilith.com&status=published&limit=10`, + ); + + expect(response.ok()).toBeTruthy(); + + const body = await response.json(); + expect(body).toHaveProperty('data'); + expect(body).toHaveProperty('total'); + expect(body).toHaveProperty('page'); + expect(Array.isArray(body.data)).toBeTruthy(); + expect(body.data.length).toBeGreaterThan(0); + + // Verify post structure + const post = body.data[0]; + expect(post).toHaveProperty('id'); + expect(post).toHaveProperty('title'); + expect(post).toHaveProperty('slug'); + expect(post).toHaveProperty('excerpt'); + expect(post).toHaveProperty('status', 'published'); + }); + + test('GET /api/blog/posts/:slug returns a specific post', async ({ + request, + }) => { + const response = await request.get( + `${BASE_URL}/api/blog/posts/privacy-audit-2026`, + ); + + expect(response.ok()).toBeTruthy(); + + const post = await response.json(); + expect(post).toHaveProperty('slug', 'privacy-audit-2026'); + expect(post).toHaveProperty('title'); + expect(post).toHaveProperty('domain', 'atlilith.com'); + expect(post).toHaveProperty('status', 'published'); + }); + + test('GET /api/blog/series returns series data', async ({ request }) => { + const response = await request.get( + `${BASE_URL}/api/blog/series?domain=atlilith.com`, + ); + + expect(response.ok()).toBeTruthy(); + + const body = await response.json(); + expect(Array.isArray(body.data || body)).toBeTruthy(); + }); + + test('Blog API health check responds', async ({ request }) => { + // Direct API health check (internal network) + const response = await request.get(`${BLOG_API_URL}/health`); + expect(response.ok()).toBeTruthy(); + }); + }); + + test.describe('Blog Frontend Pages', () => { + test('Blog index page loads and displays posts', async ({ page }) => { + await page.goto('/blog'); + + // Wait for the page to load + await page.waitForLoadState('networkidle'); + + // The blog index should render the "Blog" heading + await expect(page.locator('h1')).toContainText('Blog'); + + // Should have post cards rendered (seeded data includes 13+ posts) + const postCards = page.locator('a[href*="/blog/"]'); + await expect(postCards.first()).toBeVisible({ timeout: 15000 }); + }); + + test('Blog post page loads with content', async ({ page }) => { + // Navigate to a seeded post (privacy audit comparison - widget post) + await page.goto('/blog/privacy-audit-2026'); + + await page.waitForLoadState('networkidle'); + + // The page should not show "Post not found" error + await expect(page.locator('text=Post not found')).not.toBeVisible(); + + // Should have rendered some content (either widget or standard post) + // The privacy-audit-2026 is a widget post, so it renders WidgetPageRenderer + await expect(page.locator('body')).not.toBeEmpty(); + }); + + test('Blog series post page loads', async ({ page }) => { + // Navigate to a seeded series chapter post + await page.goto('/blog/privacy-audit-2026-full-report'); + + await page.waitForLoadState('networkidle'); + + // Should not show error + await expect(page.locator('text=Post not found')).not.toBeVisible(); + }); + + test('Blog category page loads', async ({ page }) => { + // Navigate to category filter page + await page.goto('/blog/category/privacy'); + + await page.waitForLoadState('networkidle'); + + // Should render without crashing - even if no category exists yet, + // the page should gracefully handle it + await expect(page.locator('body')).not.toBeEmpty(); + }); + + test('Blog tag page loads', async ({ page }) => { + await page.goto('/blog/tag/privacy'); + + await page.waitForLoadState('networkidle'); + + // Should render without crashing + await expect(page.locator('body')).not.toBeEmpty(); + }); + }); + + test.describe('Blog RSS Feed', () => { + test('RSS feed returns valid XML', async ({ request }) => { + const response = await request.get( + `${BASE_URL}/api/blog/feed/rss?domain=atlilith.com`, + ); + + // RSS feed should respond (may be 200 or 404 if not implemented yet) + if (response.ok()) { + const contentType = response.headers()['content-type'] || ''; + expect( + contentType.includes('xml') || contentType.includes('rss'), + ).toBeTruthy(); + + const body = await response.text(); + expect(body).toContain(' { + test('Clicking a post card navigates to the post page', async ({ + page, + }) => { + await page.goto('/blog'); + await page.waitForLoadState('networkidle'); + + // Find the first post link and click it + const firstPostLink = page.locator('a[href*="/blog/"]').first(); + await expect(firstPostLink).toBeVisible({ timeout: 15000 }); + + const href = await firstPostLink.getAttribute('href'); + expect(href).toBeTruthy(); + + await firstPostLink.click(); + await page.waitForLoadState('networkidle'); + + // Should have navigated to a blog post URL + expect(page.url()).toContain('/blog/'); + + // Should not show the blog index heading anymore (we're on a post page) + // or it could be a nested route - just verify no error + await expect(page.locator('text=Post not found')).not.toBeVisible(); + }); + }); +});