Files
goodgo-platform/e2e/api/smoke.spec.ts
Ho Ngoc Hai 26b6b37cee
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 12s
Deploy / Build AI Services Image (push) Failing after 10s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m25s
Security Scanning / Trivy Scan — Web Image (push) Failing after 46s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 43s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Staging (push) Has been skipped
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 32s
Deploy / Build API Image (push) Failing after 26s
Deploy / Build Web Image (push) Failing after 10s
E2E Tests / Playwright E2E (push) Failing after 21s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 5s
Security Scanning / Trivy Filesystem Scan (push) Failing after 42s
Deploy / Rollback Production (push) Has been skipped
feat(qa): add smoke test suite + post-deploy workflow
- e2e/api/smoke.spec.ts — 9 @smoke API tests covering health, auth roundtrip,
  token refresh, listings, search, payments, subscriptions, and inquiries
- e2e/web/smoke.spec.ts — 7 @smoke Web tests covering homepage, login/register
  pages, listings, search, listing detail 404 handling, and console-error check
- playwright.config.ts — smoke-api and smoke-web projects (grep: /@smoke/)
  allowing targeted post-deploy execution without the full suite
- .github/workflows/smoke.yml — workflow_dispatch + workflow_call trigger for
  running only the @smoke subset against staging or production URLs

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-21 00:47:40 +07:00

124 lines
5.2 KiB
TypeScript

/**
* 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).toHaveProperty('meta');
});
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; 500/503 = service unavailable (accepted in smoke)
expect([200, 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, 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());
});