/** * E2E Tests für News API * * Testet die dedizierte News-API mit Tenant-Isolation und Filtering */ import { test, expect } from '@playwright/test' // Test tenant ID (porwoll.de) const TEST_TENANT_ID = 1 test.describe('News API - List Endpoint', () => { const newsEndpoint = '/api/news' test('GET /api/news requires tenant parameter', async ({ request }) => { const response = await request.get(newsEndpoint) expect(response.status()).toBe(400) const data = await response.json() expect(data).toHaveProperty('error') expect(data.error).toContain('Tenant ID is required') }) test('GET /api/news returns 400 for invalid tenant ID', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=invalid`) expect(response.status()).toBe(400) const data = await response.json() expect(data).toHaveProperty('error') expect(data.error).toContain('Invalid tenant ID') }) test('GET /api/news returns valid response structure with tenant', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}`) expect(response.ok()).toBe(true) const data = await response.json() // Validate response structure expect(data).toHaveProperty('docs') expect(data).toHaveProperty('pagination') expect(data).toHaveProperty('filters') expect(Array.isArray(data.docs)).toBe(true) expect(data.pagination).toHaveProperty('page') expect(data.pagination).toHaveProperty('limit') expect(data.pagination).toHaveProperty('totalPages') expect(data.pagination).toHaveProperty('totalDocs') expect(data.pagination).toHaveProperty('hasNextPage') expect(data.pagination).toHaveProperty('hasPrevPage') // Filters should contain tenant expect(data.filters).toHaveProperty('tenant') expect(data.filters.tenant).toBe(TEST_TENANT_ID) }) test('GET /api/news validates type parameter', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}&type=invalid`) expect(response.status()).toBe(400) const data = await response.json() expect(data).toHaveProperty('error') expect(data.error).toContain('Invalid type') }) test('GET /api/news accepts valid type parameter', async ({ request }) => { const validTypes = ['news', 'press', 'announcement', 'blog'] for (const type of validTypes) { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}&type=${type}`) expect(response.ok()).toBe(true) const data = await response.json() expect(data.filters.type).toBe(type) // All returned docs should match the type for (const doc of data.docs) { expect(doc.type).toBe(type) } } }) test('GET /api/news supports multiple types', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}&types=news,blog`) expect(response.ok()).toBe(true) const data = await response.json() // All docs should be either news or blog for (const doc of data.docs) { expect(['news', 'blog']).toContain(doc.type) } }) test('GET /api/news respects pagination parameters', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}&page=1&limit=5`) expect(response.ok()).toBe(true) const data = await response.json() expect(data.pagination.page).toBe(1) expect(data.pagination.limit).toBe(5) expect(data.docs.length).toBeLessThanOrEqual(5) }) test('GET /api/news enforces max limit', async ({ request }) => { // Request more than max limit (50) const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}&limit=100`) expect(response.ok()).toBe(true) const data = await response.json() // Should cap at max limit expect(data.pagination.limit).toBeLessThanOrEqual(50) }) test('GET /api/news includes categories when requested', async ({ request }) => { const response = await request.get( `${newsEndpoint}?tenant=${TEST_TENANT_ID}&includeCategories=true` ) expect(response.ok()).toBe(true) const data = await response.json() expect(data).toHaveProperty('categories') expect(Array.isArray(data.categories)).toBe(true) // Each category should have id, name, slug for (const cat of data.categories) { expect(cat).toHaveProperty('id') expect(cat).toHaveProperty('name') expect(cat).toHaveProperty('slug') } }) test('GET /api/news includes archive when requested', async ({ request }) => { const response = await request.get( `${newsEndpoint}?tenant=${TEST_TENANT_ID}&includeArchive=true` ) expect(response.ok()).toBe(true) const data = await response.json() expect(data).toHaveProperty('archive') expect(Array.isArray(data.archive)).toBe(true) // Each archive entry should have year, months, total for (const entry of data.archive) { expect(entry).toHaveProperty('year') expect(entry).toHaveProperty('months') expect(entry).toHaveProperty('total') expect(Array.isArray(entry.months)).toBe(true) } }) test('GET /api/news filters by featured', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}&featured=true`) expect(response.ok()).toBe(true) const data = await response.json() expect(data.filters.featured).toBe(true) // All docs should be featured for (const doc of data.docs) { expect(doc.isFeatured).toBe(true) } }) test('GET /api/news validates year parameter', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}&year=1900`) expect(response.status()).toBe(400) const data = await response.json() expect(data.error).toContain('Invalid year') }) test('GET /api/news validates month parameter', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}&month=13`) expect(response.status()).toBe(400) const data = await response.json() expect(data.error).toContain('Invalid month') }) test('GET /api/news accepts valid locale parameter', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}&locale=en`) expect(response.ok()).toBe(true) const data = await response.json() expect(data.filters.locale).toBe('en') }) test('GET /api/news doc items have correct structure', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}`) expect(response.ok()).toBe(true) const data = await response.json() for (const doc of data.docs) { expect(doc).toHaveProperty('id') expect(doc).toHaveProperty('title') expect(doc).toHaveProperty('slug') expect(doc).toHaveProperty('type') expect(doc).toHaveProperty('excerpt') expect(doc).toHaveProperty('publishedAt') expect(doc).toHaveProperty('isFeatured') expect(doc).toHaveProperty('featuredImage') expect(doc).toHaveProperty('categories') expect(doc).toHaveProperty('seo') // Categories should be an array expect(Array.isArray(doc.categories)).toBe(true) } }) test('GET /api/news includes rate limit headers', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}`) expect(response.ok()).toBe(true) const headers = response.headers() expect(headers['x-ratelimit-remaining']).toBeDefined() }) test('GET /api/news includes cache headers', async ({ request }) => { const response = await request.get(`${newsEndpoint}?tenant=${TEST_TENANT_ID}`) expect(response.ok()).toBe(true) const headers = response.headers() expect(headers['cache-control']).toBeDefined() expect(headers['cache-control']).toContain('max-age') }) }) test.describe('News API - Detail Endpoint', () => { test('GET /api/news/[slug] requires tenant parameter', async ({ request }) => { const response = await request.get('/api/news/some-article') expect(response.status()).toBe(400) const data = await response.json() expect(data).toHaveProperty('error') expect(data.error).toContain('Tenant ID is required') }) test('GET /api/news/[slug] returns 404 for non-existent article', async ({ request }) => { const response = await request.get( `/api/news/non-existent-article-slug-12345?tenant=${TEST_TENANT_ID}` ) expect(response.status()).toBe(404) const data = await response.json() expect(data).toHaveProperty('error') expect(data.error).toContain('not found') }) test('GET /api/news/[slug] returns valid article structure', async ({ request }) => { // First get a list to find a valid slug const listResponse = await request.get(`/api/news?tenant=${TEST_TENANT_ID}&limit=1`) if (!listResponse.ok()) { test.skip() return } const listData = await listResponse.json() if (listData.docs.length === 0) { test.skip() // No articles to test with return } const slug = listData.docs[0].slug const response = await request.get(`/api/news/${slug}?tenant=${TEST_TENANT_ID}`) expect(response.ok()).toBe(true) const data = await response.json() expect(data).toHaveProperty('article') expect(data).toHaveProperty('locale') const article = data.article expect(article).toHaveProperty('id') expect(article).toHaveProperty('title') expect(article).toHaveProperty('slug') expect(article).toHaveProperty('type') expect(article).toHaveProperty('content') expect(article).toHaveProperty('seo') }) test('GET /api/news/[slug] includes related posts by default', async ({ request }) => { // First get a list to find a valid slug const listResponse = await request.get(`/api/news?tenant=${TEST_TENANT_ID}&limit=1`) if (!listResponse.ok()) { test.skip() return } const listData = await listResponse.json() if (listData.docs.length === 0) { test.skip() return } const slug = listData.docs[0].slug const response = await request.get(`/api/news/${slug}?tenant=${TEST_TENANT_ID}`) expect(response.ok()).toBe(true) const data = await response.json() // Related posts may or may not exist, but structure should be valid if (data.relatedPosts) { expect(Array.isArray(data.relatedPosts)).toBe(true) for (const related of data.relatedPosts) { expect(related).toHaveProperty('id') expect(related).toHaveProperty('title') expect(related).toHaveProperty('slug') expect(related).toHaveProperty('type') } } }) test('GET /api/news/[slug] includes navigation', async ({ request }) => { // First get a list to find a valid slug const listResponse = await request.get(`/api/news?tenant=${TEST_TENANT_ID}&limit=1`) if (!listResponse.ok()) { test.skip() return } const listData = await listResponse.json() if (listData.docs.length === 0) { test.skip() return } const slug = listData.docs[0].slug const response = await request.get(`/api/news/${slug}?tenant=${TEST_TENANT_ID}`) expect(response.ok()).toBe(true) const data = await response.json() expect(data).toHaveProperty('navigation') expect(data.navigation).toHaveProperty('previous') expect(data.navigation).toHaveProperty('next') }) test('GET /api/news/[slug] can exclude related posts', async ({ request }) => { const listResponse = await request.get(`/api/news?tenant=${TEST_TENANT_ID}&limit=1`) if (!listResponse.ok()) { test.skip() return } const listData = await listResponse.json() if (listData.docs.length === 0) { test.skip() return } const slug = listData.docs[0].slug const response = await request.get( `/api/news/${slug}?tenant=${TEST_TENANT_ID}&includeRelated=false` ) expect(response.ok()).toBe(true) const data = await response.json() // Related posts should not be included expect(data.relatedPosts).toBeUndefined() }) }) test.describe('News API - Tenant Isolation', () => { test('Different tenants return isolated data', async ({ request }) => { // Get news for tenant 1 const response1 = await request.get('/api/news?tenant=1') // Get news for tenant 4 (C2S) const response4 = await request.get('/api/news?tenant=4') expect(response1.ok()).toBe(true) expect(response4.ok()).toBe(true) const data1 = await response1.json() const data4 = await response4.json() // Both should have valid structure expect(data1).toHaveProperty('docs') expect(data4).toHaveProperty('docs') // Filter responses should reflect tenant expect(data1.filters.tenant).toBe(1) expect(data4.filters.tenant).toBe(4) }) test('Cannot access news from non-existent tenant', async ({ request }) => { const response = await request.get('/api/news?tenant=99999') expect(response.ok()).toBe(true) const data = await response.json() // Should return empty results, not error expect(data.docs).toEqual([]) expect(data.pagination.totalDocs).toBe(0) }) })