import { test, expect } from '@playwright/test' test.describe('Search API', () => { test('GET /api/search returns valid response structure', async ({ request }) => { const response = await request.get('/api/search?q=test') expect(response.ok()).toBe(true) const data = await response.json() // Validate response structure expect(data).toHaveProperty('results') expect(data).toHaveProperty('total') expect(data).toHaveProperty('query') expect(data).toHaveProperty('filters') expect(data).toHaveProperty('pagination') expect(Array.isArray(data.results)).toBe(true) expect(typeof data.total).toBe('number') expect(data.query).toBe('test') expect(data.pagination).toHaveProperty('limit') expect(data.pagination).toHaveProperty('offset') expect(data.pagination).toHaveProperty('hasMore') }) test('GET /api/search validates minimum query length', async ({ request }) => { const response = await request.get('/api/search?q=a') expect(response.status()).toBe(400) const data = await response.json() expect(data).toHaveProperty('error') expect(data.error).toContain('at least 2 characters') }) test('GET /api/search validates maximum query length', async ({ request }) => { const longQuery = 'a'.repeat(101) const response = await request.get(`/api/search?q=${longQuery}`) expect(response.status()).toBe(400) const data = await response.json() expect(data).toHaveProperty('error') expect(data.error).toContain('at most 100 characters') }) test('GET /api/search validates type parameter', async ({ request }) => { const response = await request.get('/api/search?q=test&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/search respects limit parameter', async ({ request }) => { const response = await request.get('/api/search?q=test&limit=5') expect(response.ok()).toBe(true) const data = await response.json() expect(data.pagination.limit).toBe(5) expect(data.results.length).toBeLessThanOrEqual(5) }) test('GET /api/search includes rate limit headers', async ({ request }) => { const response = await request.get('/api/search?q=test') expect(response.headers()['x-ratelimit-remaining']).toBeDefined() }) }) test.describe('Suggestions API', () => { test('GET /api/search/suggestions returns valid response structure', async ({ request }) => { const response = await request.get('/api/search/suggestions?q=test') expect(response.ok()).toBe(true) const data = await response.json() expect(data).toHaveProperty('suggestions') expect(Array.isArray(data.suggestions)).toBe(true) }) test('GET /api/search/suggestions returns empty for short query', async ({ request }) => { const response = await request.get('/api/search/suggestions?q=a') expect(response.ok()).toBe(true) const data = await response.json() expect(data.suggestions).toEqual([]) }) test('GET /api/search/suggestions respects limit parameter', async ({ request }) => { const response = await request.get('/api/search/suggestions?q=test&limit=3') expect(response.ok()).toBe(true) const data = await response.json() expect(data.suggestions.length).toBeLessThanOrEqual(3) }) test('GET /api/search/suggestions suggestion items have correct structure', async ({ request, }) => { const response = await request.get('/api/search/suggestions?q=test') expect(response.ok()).toBe(true) const data = await response.json() for (const suggestion of data.suggestions) { expect(suggestion).toHaveProperty('title') expect(suggestion).toHaveProperty('slug') expect(suggestion).toHaveProperty('type') } }) }) test.describe('Posts API', () => { test('GET /api/posts returns valid response structure', async ({ request }) => { const response = await request.get('/api/posts') expect(response.ok()).toBe(true) const data = await response.json() 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') }) test('GET /api/posts validates type parameter', async ({ request }) => { const response = await request.get('/api/posts?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/posts respects pagination parameters', async ({ request }) => { const response = await request.get('/api/posts?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/posts filters by type', async ({ request }) => { const response = await request.get('/api/posts?type=blog') expect(response.ok()).toBe(true) const data = await response.json() expect(data.filters.type).toBe('blog') // All returned posts should be of type blog for (const post of data.docs) { expect(post.type).toBe('blog') } }) test('GET /api/posts includes rate limit headers', async ({ request }) => { const response = await request.get('/api/posts') expect(response.headers()['x-ratelimit-remaining']).toBeDefined() }) test('GET /api/posts doc items have correct structure', async ({ request }) => { const response = await request.get('/api/posts') expect(response.ok()).toBe(true) const data = await response.json() for (const post of data.docs) { expect(post).toHaveProperty('id') expect(post).toHaveProperty('title') expect(post).toHaveProperty('slug') expect(post).toHaveProperty('type') // Optional fields expect('excerpt' in post).toBe(true) expect('publishedAt' in post).toBe(true) expect('featuredImage' in post).toBe(true) expect('category' in post).toBe(true) } }) })