/** * Smoke suite — API * * Runs after every deploy to verify core API paths are healthy. * Tests are tagged with @smoke so they can be isolated: * * npx playwright test --project=api --grep @smoke * * Intentionally lightweight: each test makes at most one API call and * asserts only on status code + structural shape. No heavy fixtures, * no side effects that require cleanup. */ import { test, expect } from '@playwright/test'; import { createTestUser, registerUser } from '../fixtures'; // ── Health ──────────────────────────────────────────────────────────────────── test('@smoke API health check returns 200', async ({ request }) => { // Playwright baseURL ends with /api/v1/ — health is at the root const res = await request.get('../../health'); expect(res.status()).toBe(200); const body = await res.json(); expect(body.status).toBe('ok'); }); // ── Auth ────────────────────────────────────────────────────────────────────── test('@smoke auth register + login roundtrip', async ({ request }) => { const user = createTestUser(); const registered = await registerUser(request, user); expect(registered).toHaveProperty('accessToken'); expect(registered).toHaveProperty('refreshToken'); expect(typeof registered.accessToken).toBe('string'); const loginRes = await request.post('auth/login', { data: { phone: user.phone, password: user.password }, }); expect(loginRes.status()).toBe(201); const tokens = await loginRes.json(); expect(tokens).toHaveProperty('accessToken'); }); test('@smoke auth token refresh', async ({ request }) => { const user = createTestUser(); const { refreshToken } = await registerUser(request, user); const res = await request.post('auth/refresh', { data: { refreshToken }, }); expect(res.status()).toBe(201); const body = await res.json(); expect(body).toHaveProperty('accessToken'); }); test('@smoke protected route rejects unauthenticated request', async ({ request }) => { const res = await request.get('auth/profile'); expect([401, 403]).toContain(res.status()); }); // ── Listings ───────────────────────────────────────────────────────────────── test('@smoke listings list returns paginated results', async ({ request }) => { const res = await request.get('listings', { params: { page: 1, limit: 5 }, }); expect(res.status()).toBe(200); const body = await res.json(); expect(body).toHaveProperty('data'); expect(Array.isArray(body.data)).toBeTruthy(); expect(body.meta ?? body).toHaveProperty('page'); expect(body.meta ?? body).toHaveProperty('total'); }); test('@smoke listing creation requires auth', async ({ request }) => { const res = await request.post('listings', { data: { title: 'Smoke test listing' }, }); expect([401, 403]).toContain(res.status()); }); // ── Search ──────────────────────────────────────────────────────────────────── test('@smoke search endpoint is reachable', async ({ request }) => { const res = await request.get('search', { params: { q: 'apartment', limit: 5 }, }); // 200 = Typesense available; 400 = validation-level rejection; 500/503 = service unavailable. expect([200, 400, 500, 503]).toContain(res.status()); }); test('@smoke geo search endpoint is reachable', async ({ request }) => { const res = await request.get('search/geo', { params: { lat: 10.7769, lng: 106.7009, radius: 5000, limit: 5 }, }); expect([200, 400, 500, 503]).toContain(res.status()); }); // ── Payments ────────────────────────────────────────────────────────────────── test('@smoke payment initiation requires auth', async ({ request }) => { const res = await request.post('payments', { data: { amount: 100000, gateway: 'VNPAY' }, }); expect([401, 403]).toContain(res.status()); }); // ── Subscriptions ───────────────────────────────────────────────────────────── test('@smoke subscription plans are publicly readable', async ({ request }) => { const res = await request.get('subscriptions/plans'); expect(res.status()).toBe(200); const body = await res.json(); expect(Array.isArray(body)).toBeTruthy(); }); // ── Inquiries ───────────────────────────────────────────────────────────────── test('@smoke inquiry creation requires auth', async ({ request }) => { const res = await request.post('inquiries', { data: { listingId: 'smoke-listing-id', message: 'Smoke test inquiry' }, }); expect([401, 403]).toContain(res.status()); });