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