import { test, expect, request } from "@playwright/test"; const API_BASE = "http://localhost:3000/api"; async function getAuthToken(): Promise { const ctx = await request.newContext(); const res = await ctx.post(`${API_BASE}/auth/login`, { data: { email: "testtutora2026@example.com", password: "Test1234!", }, }); if (!res.ok()) return null; const data = await res.json(); return data.access_token || null; } async function getCompanyAuthToken(): Promise { const ctx = await request.newContext(); const res = await ctx.post(`${API_BASE}/auth/login`, { data: { email: "testcompany@example.com", password: "TestPassword123!", }, }); if (!res.ok()) return null; const data = await res.json(); return data.access_token || null; } test.describe("AI API Endpoints", () => { let companyToken: string | null; let jobSeekerToken: string | null; test.beforeAll(async () => { companyToken = await getCompanyAuthToken(); jobSeekerToken = await getAuthToken(); }); test.describe("Company AI - Generate Job Field", () => { test("POST /ai/generate-job-field returns generated content", async ({ page }) => { if (!companyToken) test.skip(); await page.goto(`${API_BASE}/`); await page.evaluate((t: string) => { window.sessionStorage.setItem("nxtgauge_access_token", t); }, companyToken); const ctx = await request.newContext(); const res = await ctx.post(`${API_BASE}/ai/generate-job-field`, { headers: { Authorization: `Bearer ${companyToken}`, }, data: { field: "title", prompt: "Generate a job title for a senior frontend developer position", }, }); if (res.status() === 404) { test.skip(); return; } expect(res.status()).toBe(200); const body = await res.json(); expect(body).toHaveProperty("field", "title"); expect(body).toHaveProperty("content"); expect(typeof body.content).toBe("string"); expect(body.content.length).toBeGreaterThan(0); }); test("POST /ai/generate-job-field rejects invalid field", async () => { if (!companyToken) test.skip(); const ctx = await request.newContext(); const res = await ctx.post(`${API_BASE}/ai/generate-job-field`, { headers: { Authorization: `Bearer ${companyToken}`, }, data: { field: "invalid_field", prompt: "test", }, }); if (res.status() === 404) test.skip(); else expect(res.status()).toBe(400); }); test("POST /ai/generate-job-field rate limits after daily quota", async () => { if (!companyToken) test.skip(); const ctx = await request.newContext(); let got429 = false; for (let i = 0; i < 6; i++) { const res = await ctx.post(`${API_BASE}/ai/generate-job-field`, { headers: { Authorization: `Bearer ${companyToken}`, }, data: { field: "title", prompt: `Test prompt ${i}`, }, }); if (res.status() === 404) { test.skip(); return; } if (res.status() === 429) { got429 = true; const body = await res.json(); expect(body.code).toBe("AI_LIMIT_EXCEEDED"); break; } } if (!got429) { console.warn("Did not hit rate limit within 6 requests - this may indicate the feature is not working or limit is higher than expected"); } }); test("POST /ai/generate-job-field returns 401 without auth when route exists", async () => { const ctx = await request.newContext(); const res = await ctx.post(`${API_BASE}/ai/generate-job-field`, { data: { field: "title", prompt: "test", }, }); if (res.status() === 404) { test.skip(); return; } expect(res.status()).toBe(401); }); }); test.describe("AI Usage Endpoint", () => { test("GET /ai/usage returns usage stats for company", async () => { if (!companyToken) test.skip(); const ctx = await request.newContext(); const res = await ctx.get(`${API_BASE}/ai/usage`, { headers: { Authorization: `Bearer ${companyToken}`, }, }); if (res.status() === 404) { test.skip(); return; } expect(res.status()).toBe(200); const body = await res.json(); expect(body).toHaveProperty("used_today"); expect(body).toHaveProperty("limit"); expect(body).toHaveProperty("has_ai_pack"); expect(typeof body.used_today).toBe("number"); expect(typeof body.limit).toBe("number"); }); test("GET /ai/usage returns 401 without auth when route exists", async () => { const ctx = await request.newContext(); const res = await ctx.get(`${API_BASE}/ai/usage`); if (res.status() === 404) { test.skip(); return; } expect(res.status()).toBe(401); }); }); }); test.describe("Auth API Endpoints", () => { test("POST /auth/login returns token for valid credentials", async () => { const ctx = await request.newContext(); const res = await ctx.post(`${API_BASE}/auth/login`, { data: { email: "testtutora2026@example.com", password: "Test1234!", }, }); if (res.status() === 429) { test.skip(); return; } expect(res.status()).toBe(200); const body = await res.json(); expect(body).toHaveProperty("access_token"); expect(typeof body.access_token).toBe("string"); }); test("POST /auth/login returns 401 for invalid credentials", async () => { const ctx = await request.newContext(); const res = await ctx.post(`${API_BASE}/auth/login`, { data: { email: "invalid@example.com", password: "wrongpassword", }, }); if (res.status() === 429) test.skip(); else expect(res.status()).toBe(401); }); test("POST /auth/login rate limits after too many attempts", async () => { const ctx = await request.newContext(); for (let i = 0; i < 6; i++) { await ctx.post(`${API_BASE}/auth/login`, { data: { email: "testtutora2026@example.com", password: "wrongpassword", }, }); } const res = await ctx.post(`${API_BASE}/auth/login`, { data: { email: "testtutora2026@example.com", password: "Test1234!", }, }); expect(res.status()).toBe(429); const body = await res.json(); expect(body.code).toBe("RATE_LIMITED"); }); }); test.describe("Gateway API", () => { test("Gateway routes /api/ai/* to users service", async () => { const ctx = await request.newContext(); const res = await ctx.get(`${API_BASE}/ai/usage`, { headers: { Authorization: `Bearer dummy`, }, }); if (res.status() === 404) { test.skip(); return; } expect(res.status()).not.toBe(404); }); test("Gateway returns 404 for unknown routes", async () => { const ctx = await request.newContext(); const res = await ctx.get(`${API_BASE}/nonexistent-route`); expect(res.status()).toBe(404); }); });