test(platform-service): add subscriptions route-level tests — 395 tests, 39.6% stmts
This commit is contained in:
parent
1cb9af3ae4
commit
c7934af227
@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Route-level tests for subscriptions module — Fastify inject.
|
||||
*/
|
||||
|
||||
import Fastify from 'fastify';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const repoMock = {
|
||||
getByUserId: vi.fn(),
|
||||
getByStripeCustomerId: vi.fn(),
|
||||
getByStripeCustomerIdAnyProduct: vi.fn(),
|
||||
createSubscription: vi.fn(),
|
||||
updateSubscription: vi.fn(),
|
||||
getPaymentsByUser: vi.fn(),
|
||||
createPayment: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock('./repository.js', () => repoMock);
|
||||
|
||||
vi.mock('../../lib/request-context.js', () => ({
|
||||
getRequestProductId: () => 'lysnrai',
|
||||
}));
|
||||
|
||||
const baseSub = {
|
||||
id: 'sub_user_1_123',
|
||||
productId: 'lysnrai',
|
||||
userId: 'user_1',
|
||||
plan: 'pro',
|
||||
status: 'active',
|
||||
currentPeriodStart: '2026-02-01T00:00:00Z',
|
||||
currentPeriodEnd: '2026-03-01T00:00:00Z',
|
||||
cancelAtPeriodEnd: false,
|
||||
monthlyPrice: 9.99,
|
||||
tokensIncluded: 100000,
|
||||
tokensUsed: 5000,
|
||||
createdAt: '2026-02-01T00:00:00Z',
|
||||
updatedAt: '2026-02-01T00:00:00Z',
|
||||
};
|
||||
|
||||
describe('subscriptionRoutes', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('GET /subscriptions/:userId returns subscription', async () => {
|
||||
repoMock.getByUserId.mockResolvedValue(baseSub);
|
||||
const { subscriptionRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(subscriptionRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/api/subscriptions/user_1' });
|
||||
expect(res.statusCode).toBe(200);
|
||||
const data = JSON.parse(res.body);
|
||||
expect(data.plan).toBe('pro');
|
||||
});
|
||||
|
||||
it('GET /subscriptions/:userId returns 404 when not found', async () => {
|
||||
repoMock.getByUserId.mockResolvedValue(null);
|
||||
const { subscriptionRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(subscriptionRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/api/subscriptions/user_1' });
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it('POST /subscriptions creates a subscription', async () => {
|
||||
repoMock.createSubscription.mockResolvedValue(baseSub);
|
||||
const { subscriptionRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(subscriptionRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/subscriptions',
|
||||
payload: {
|
||||
userId: 'user_1',
|
||||
plan: 'pro',
|
||||
status: 'active',
|
||||
monthlyPrice: 9.99,
|
||||
tokensIncluded: 100000,
|
||||
},
|
||||
});
|
||||
expect(res.statusCode).toBe(201);
|
||||
});
|
||||
|
||||
it('POST /subscriptions with trial days', async () => {
|
||||
repoMock.createSubscription.mockResolvedValue(baseSub);
|
||||
const { subscriptionRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(subscriptionRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/subscriptions',
|
||||
payload: {
|
||||
userId: 'user_1',
|
||||
plan: 'pro',
|
||||
status: 'trialing',
|
||||
monthlyPrice: 9.99,
|
||||
tokensIncluded: 100000,
|
||||
trialDays: 14,
|
||||
},
|
||||
});
|
||||
expect(res.statusCode).toBe(201);
|
||||
});
|
||||
|
||||
it('POST /subscriptions rejects invalid input', async () => {
|
||||
const { subscriptionRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(subscriptionRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/subscriptions',
|
||||
payload: { plan: 'invalid' },
|
||||
});
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it('PUT /subscriptions/:userId updates subscription', async () => {
|
||||
repoMock.getByUserId.mockResolvedValue(baseSub);
|
||||
repoMock.updateSubscription.mockResolvedValue({ ...baseSub, plan: 'enterprise' });
|
||||
const { subscriptionRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(subscriptionRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/subscriptions/user_1',
|
||||
payload: { plan: 'enterprise' },
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
const data = JSON.parse(res.body);
|
||||
expect(data.plan).toBe('enterprise');
|
||||
});
|
||||
|
||||
it('PUT /subscriptions/:userId returns 404 when not found', async () => {
|
||||
repoMock.getByUserId.mockResolvedValue(null);
|
||||
const { subscriptionRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(subscriptionRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/subscriptions/user_1',
|
||||
payload: { plan: 'enterprise' },
|
||||
});
|
||||
expect(res.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it('GET /payments/:userId lists payments', async () => {
|
||||
const payment = { id: 'pay_1', amount: 999, currency: 'usd', status: 'succeeded' };
|
||||
repoMock.getPaymentsByUser.mockResolvedValue([payment]);
|
||||
const { subscriptionRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(subscriptionRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/api/payments/user_1' });
|
||||
expect(res.statusCode).toBe(200);
|
||||
const data = JSON.parse(res.body);
|
||||
expect(data.payments).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('POST /payments records a payment', async () => {
|
||||
const payment = { id: 'pay_1', amount: 999, currency: 'usd', status: 'succeeded' };
|
||||
repoMock.createPayment.mockResolvedValue(payment);
|
||||
const { subscriptionRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(subscriptionRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/payments',
|
||||
payload: {
|
||||
userId: 'user_1',
|
||||
amount: 999,
|
||||
currency: 'usd',
|
||||
status: 'succeeded',
|
||||
description: 'Pro plan',
|
||||
method: 'card',
|
||||
},
|
||||
});
|
||||
expect(res.statusCode).toBe(201);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user