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