test(platform-service): add usage route-level tests — 386 tests, 37.9% stmts
This commit is contained in:
parent
149d97dd9f
commit
1cb9af3ae4
155
services/platform-service/src/modules/usage/routes.test.ts
Normal file
155
services/platform-service/src/modules/usage/routes.test.ts
Normal file
@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Route-level tests for usage module — Fastify inject.
|
||||
*/
|
||||
|
||||
import Fastify from 'fastify';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const repoMock = {
|
||||
list: vi.fn(),
|
||||
upsert: vi.fn(),
|
||||
getMonthlyUsage: vi.fn(),
|
||||
getByDate: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mock('./repository.js', () => repoMock);
|
||||
|
||||
vi.mock('../../lib/request-context.js', () => ({
|
||||
getRequestProductId: () => 'lysnrai',
|
||||
}));
|
||||
|
||||
const baseUsage = {
|
||||
id: 'usg_2026-02-16_user_1',
|
||||
productId: 'lysnrai',
|
||||
userId: 'user_1',
|
||||
date: '2026-02-16',
|
||||
dictations: 5,
|
||||
words: 250,
|
||||
durationMs: 30000,
|
||||
tokensUsed: 1200,
|
||||
costUsd: 0.01,
|
||||
model: 'gpt-4o-mini',
|
||||
source: 'desktop',
|
||||
createdAt: '2026-02-16T00:00:00Z',
|
||||
};
|
||||
|
||||
describe('usageRoutes', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('GET /usage returns usage records', async () => {
|
||||
repoMock.list.mockResolvedValue([baseUsage]);
|
||||
const { usageRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(usageRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/api/usage' });
|
||||
expect(res.statusCode).toBe(200);
|
||||
const data = JSON.parse(res.body);
|
||||
expect(data.records).toHaveLength(1);
|
||||
expect(data.count).toBe(1);
|
||||
});
|
||||
|
||||
it('GET /usage/summary returns aggregated summary', async () => {
|
||||
repoMock.list.mockResolvedValue([baseUsage]);
|
||||
const { usageRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(usageRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({ method: 'GET', url: '/api/usage/summary' });
|
||||
expect(res.statusCode).toBe(200);
|
||||
const data = JSON.parse(res.body);
|
||||
expect(data.totalWords).toBe(250);
|
||||
expect(data.totalDictations).toBe(5);
|
||||
expect(data.totalTokens).toBe(1200);
|
||||
expect(data.modelBreakdown).toHaveLength(1);
|
||||
expect(data.sourceBreakdown).toHaveLength(1);
|
||||
expect(data.productBreakdown).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('POST /usage upserts a usage record', async () => {
|
||||
repoMock.upsert.mockResolvedValue(baseUsage);
|
||||
const { usageRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(usageRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/usage',
|
||||
payload: {
|
||||
userId: 'user_1',
|
||||
date: '2026-02-16',
|
||||
dictations: 5,
|
||||
words: 250,
|
||||
tokensUsed: 1200,
|
||||
},
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(repoMock.upsert).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('POST /usage rejects invalid input', async () => {
|
||||
const { usageRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(usageRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/usage',
|
||||
payload: { date: 'invalid' },
|
||||
});
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it('POST /usage/check-limits returns allowed status', async () => {
|
||||
repoMock.getMonthlyUsage.mockResolvedValue({ tokens: 500, words: 200, dictations: 5 });
|
||||
const { usageRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(usageRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/usage/check-limits',
|
||||
payload: { userId: 'user_1', plan: 'free' },
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
const data = JSON.parse(res.body);
|
||||
expect(data.allowed).toBe(true);
|
||||
expect(data.exceeded).toEqual([]);
|
||||
});
|
||||
|
||||
it('POST /usage/check-limits detects exceeded limits', async () => {
|
||||
repoMock.getMonthlyUsage.mockResolvedValue({ tokens: 20000, words: 10000, dictations: 200 });
|
||||
const { usageRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(usageRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/usage/check-limits',
|
||||
payload: { userId: 'user_1', plan: 'free' },
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
const data = JSON.parse(res.body);
|
||||
expect(data.allowed).toBe(false);
|
||||
expect(data.exceeded.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('POST /usage/check-limits rejects invalid plan', async () => {
|
||||
const { usageRoutes } = await import('./routes.js');
|
||||
const app = Fastify({ logger: false });
|
||||
await app.register(usageRoutes, { prefix: '/api' });
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/usage/check-limits',
|
||||
payload: { userId: 'user_1', plan: 'invalid' },
|
||||
});
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user