import { test, expect, registerUser } from '../fixtures'; test.describe('Subscriptions API', () => { let accessToken: string; test.beforeAll(async ({ request }) => { const { accessToken: token } = await registerUser(request); accessToken = token; }); test.describe('GET /subscriptions/plans — List plans', () => { test('returns all available subscription plans', async ({ request }) => { const res = await request.get('subscriptions/plans'); expect(res.status()).toBe(200); const body = await res.json(); expect(Array.isArray(body)).toBeTruthy(); expect(body.length).toBeGreaterThan(0); // Verify plan structure const plan = body[0]; expect(plan).toHaveProperty('tier'); expect(plan).toHaveProperty('name'); expect(plan).toHaveProperty('priceMonthlyVND'); }); test('includes FREE tier in plans', async ({ request }) => { const res = await request.get('subscriptions/plans'); const body = await res.json(); const freePlan = body.find((p: { tier: string }) => p.tier === 'FREE'); expect(freePlan).toBeDefined(); }); }); test.describe('GET /subscriptions/plans/:tier — Get specific plan', () => { test('returns plan details for FREE tier', async ({ request }) => { const res = await request.get('subscriptions/plans/FREE'); expect(res.status()).toBe(200); const body = await res.json(); expect(body.tier).toBe('FREE'); expect(body).toHaveProperty('name'); expect(body).toHaveProperty('maxListings'); }); test('returns plan details for AGENT_PRO tier', async ({ request }) => { const res = await request.get('subscriptions/plans/AGENT_PRO'); expect(res.status()).toBe(200); const body = await res.json(); expect(body.tier).toBe('AGENT_PRO'); }); test('returns 404 for non-existent tier', async ({ request }) => { const res = await request.get('subscriptions/plans/NONEXISTENT'); expect(res.ok()).toBeFalsy(); expect([404, 400]).toContain(res.status()); }); }); test.describe('POST /subscriptions — Create subscription', () => { test('creates a FREE subscription', async ({ request }) => { const res = await request.post('subscriptions', { data: { planTier: 'FREE', billingCycle: 'monthly' }, headers: { Authorization: `Bearer ${accessToken}` }, }); // May succeed or conflict if user already has subscription expect([201, 409]).toContain(res.status()); if (res.status() === 201) { const body = await res.json(); expect(body).toHaveProperty('subscriptionId'); expect(body).toHaveProperty('planTier'); } }); test('rejects subscription with invalid plan tier', async ({ request }) => { const res = await request.post('subscriptions', { data: { planTier: 'INVALID_TIER', billingCycle: 'monthly' }, headers: { Authorization: `Bearer ${accessToken}` }, }); expect(res.ok()).toBeFalsy(); expect(res.status()).toBe(400); }); test('rejects subscription with invalid billing cycle', async ({ request }) => { const res = await request.post('subscriptions', { data: { planTier: 'FREE', billingCycle: 'weekly' }, headers: { Authorization: `Bearer ${accessToken}` }, }); expect(res.ok()).toBeFalsy(); expect(res.status()).toBe(400); }); test('rejects unauthenticated subscription creation', async ({ request }) => { const res = await request.post('subscriptions', { data: { planTier: 'FREE', billingCycle: 'monthly' }, }); expect(res.status()).toBe(401); }); }); test.describe('GET /subscriptions/quota/:metric — Check quota', () => { test('returns quota for listings metric', async ({ request }) => { // Ensure user has a subscription first await request.post('subscriptions', { data: { planTier: 'FREE', billingCycle: 'monthly' }, headers: { Authorization: `Bearer ${accessToken}` }, }); const res = await request.get('subscriptions/quota/listings', { headers: { Authorization: `Bearer ${accessToken}` }, }); // 200 if subscription exists, 404 if no subscription if (res.status() === 200) { const body = await res.json(); expect(body).toHaveProperty('limit'); expect(body).toHaveProperty('used'); expect(body).toHaveProperty('remaining'); } }); test('rejects unauthenticated quota check', async ({ request }) => { const res = await request.get('subscriptions/quota/listings'); expect(res.status()).toBe(401); }); }); test.describe('POST /subscriptions/usage — Meter usage', () => { test('meters usage for authenticated user', async ({ request }) => { const res = await request.post('subscriptions/usage', { data: { metric: 'listings', count: 1 }, headers: { Authorization: `Bearer ${accessToken}` }, }); // 200/201 if subscription exists, 404 if not expect([200, 201, 404]).toContain(res.status()); }); test('rejects usage with invalid count', async ({ request }) => { const res = await request.post('subscriptions/usage', { data: { metric: 'listings', count: -1 }, headers: { Authorization: `Bearer ${accessToken}` }, }); expect(res.ok()).toBeFalsy(); expect(res.status()).toBe(400); }); }); test.describe('GET /subscriptions/billing — Billing history', () => { test('returns billing history for authenticated user', async ({ request }) => { const res = await request.get('subscriptions/billing', { headers: { Authorization: `Bearer ${accessToken}` }, }); expect(res.status()).toBe(200); const body = await res.json(); // Response contains subscription, payments, and total expect(body).toHaveProperty('payments'); expect(Array.isArray(body.payments)).toBeTruthy(); }); test('supports pagination', async ({ request }) => { const res = await request.get('subscriptions/billing', { params: { limit: 5, offset: 0 }, headers: { Authorization: `Bearer ${accessToken}` }, }); expect(res.status()).toBe(200); }); test('rejects unauthenticated billing request', async ({ request }) => { const res = await request.get('subscriptions/billing'); expect(res.status()).toBe(401); }); }); test.describe('PUT /subscriptions/upgrade — Upgrade subscription', () => { test('rejects unauthenticated upgrade', async ({ request }) => { const res = await request.put('subscriptions/upgrade', { data: { newPlanTier: 'AGENT_PRO' }, }); expect(res.status()).toBe(401); }); }); test.describe('DELETE /subscriptions — Cancel subscription', () => { test('rejects unauthenticated cancellation', async ({ request }) => { const res = await request.delete('subscriptions'); expect(res.status()).toBe(401); }); }); });