test(platform-service): add comments route-level tests
This commit is contained in:
parent
3e1647ae96
commit
e4ee23aab4
182
services/platform-service/src/modules/comments/routes.test.ts
Normal file
182
services/platform-service/src/modules/comments/routes.test.ts
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* Route-level tests for comments module — Fastify inject.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Fastify from 'fastify';
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
const commentRepoMock = {
|
||||||
|
listByItem: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
getById: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
remove: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemRepoMock = {
|
||||||
|
getById: vi.fn(),
|
||||||
|
incrementCommentCount: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const authMock = {
|
||||||
|
extractAuth: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mock('./repository.js', () => commentRepoMock);
|
||||||
|
vi.mock('../items/repository.js', () => itemRepoMock);
|
||||||
|
vi.mock('../../lib/auth.js', () => authMock);
|
||||||
|
|
||||||
|
const baseItem = {
|
||||||
|
id: 'item_1',
|
||||||
|
productId: 'lysnrai',
|
||||||
|
title: 'Feature request',
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseComment = {
|
||||||
|
id: 'cmt_1',
|
||||||
|
itemId: 'item_1',
|
||||||
|
productId: 'lysnrai',
|
||||||
|
authorId: 'user_1',
|
||||||
|
authorEmail: 'user1@example.com',
|
||||||
|
body: 'Looks good',
|
||||||
|
createdAt: '2026-02-16T00:00:00Z',
|
||||||
|
updatedAt: '2026-02-16T00:00:00Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('commentRoutes', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
authMock.extractAuth.mockResolvedValue({ sub: 'user_1', email: 'user1@example.com', role: 'user' });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('GET /items/:itemId/comments returns list', async () => {
|
||||||
|
itemRepoMock.getById.mockResolvedValue(baseItem);
|
||||||
|
commentRepoMock.listByItem.mockResolvedValue([baseComment]);
|
||||||
|
|
||||||
|
const { commentRoutes } = await import('./routes.js');
|
||||||
|
const app = Fastify({ logger: false });
|
||||||
|
await app.register(commentRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
|
const res = await app.inject({ method: 'GET', url: '/api/items/item_1/comments' });
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const data = JSON.parse(res.body);
|
||||||
|
expect(data.comments).toHaveLength(1);
|
||||||
|
expect(data.count).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('POST /items/:itemId/comments creates comment', async () => {
|
||||||
|
itemRepoMock.getById.mockResolvedValue(baseItem);
|
||||||
|
commentRepoMock.create.mockResolvedValue(baseComment);
|
||||||
|
|
||||||
|
const { commentRoutes } = await import('./routes.js');
|
||||||
|
const app = Fastify({ logger: false });
|
||||||
|
await app.register(commentRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/items/item_1/comments',
|
||||||
|
payload: { body: 'Looks good' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(201);
|
||||||
|
expect(itemRepoMock.incrementCommentCount).toHaveBeenCalledWith('item_1', 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('POST /items/:itemId/comments returns 400 for invalid payload', async () => {
|
||||||
|
itemRepoMock.getById.mockResolvedValue(baseItem);
|
||||||
|
|
||||||
|
const { commentRoutes } = await import('./routes.js');
|
||||||
|
const app = Fastify({ logger: false });
|
||||||
|
await app.register(commentRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/api/items/item_1/comments',
|
||||||
|
payload: { body: '' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('PUT /items/:itemId/comments/:id updates comment for author', async () => {
|
||||||
|
commentRepoMock.getById.mockResolvedValue(baseComment);
|
||||||
|
commentRepoMock.update.mockResolvedValue({ ...baseComment, body: 'Updated' });
|
||||||
|
|
||||||
|
const { commentRoutes } = await import('./routes.js');
|
||||||
|
const app = Fastify({ logger: false });
|
||||||
|
await app.register(commentRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'PUT',
|
||||||
|
url: '/api/items/item_1/comments/cmt_1',
|
||||||
|
payload: { body: 'Updated' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const data = JSON.parse(res.body);
|
||||||
|
expect(data.body).toBe('Updated');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('PUT /items/:itemId/comments/:id returns 403 for non-author user', async () => {
|
||||||
|
authMock.extractAuth.mockResolvedValue({ sub: 'user_2', email: 'user2@example.com', role: 'user' });
|
||||||
|
commentRepoMock.getById.mockResolvedValue(baseComment);
|
||||||
|
|
||||||
|
const { commentRoutes } = await import('./routes.js');
|
||||||
|
const app = Fastify({ logger: false });
|
||||||
|
await app.register(commentRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
|
const res = await app.inject({
|
||||||
|
method: 'PUT',
|
||||||
|
url: '/api/items/item_1/comments/cmt_1',
|
||||||
|
payload: { body: 'Updated' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(403);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('DELETE /items/:itemId/comments/:id deletes for author', async () => {
|
||||||
|
commentRepoMock.getById.mockResolvedValue(baseComment);
|
||||||
|
commentRepoMock.remove.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const { commentRoutes } = await import('./routes.js');
|
||||||
|
const app = Fastify({ logger: false });
|
||||||
|
await app.register(commentRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
|
const res = await app.inject({ method: 'DELETE', url: '/api/items/item_1/comments/cmt_1' });
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
const data = JSON.parse(res.body);
|
||||||
|
expect(data.success).toBe(true);
|
||||||
|
expect(itemRepoMock.incrementCommentCount).toHaveBeenCalledWith('item_1', -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('DELETE /items/:itemId/comments/:id allows admin to delete', async () => {
|
||||||
|
authMock.extractAuth.mockResolvedValue({ sub: 'admin_1', email: 'admin@example.com', role: 'admin' });
|
||||||
|
commentRepoMock.getById.mockResolvedValue(baseComment);
|
||||||
|
|
||||||
|
const { commentRoutes } = await import('./routes.js');
|
||||||
|
const app = Fastify({ logger: false });
|
||||||
|
await app.register(commentRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
|
const res = await app.inject({ method: 'DELETE', url: '/api/items/item_1/comments/cmt_1' });
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('DELETE /items/:itemId/comments/:id returns 404 when comment missing', async () => {
|
||||||
|
commentRepoMock.getById.mockResolvedValue(null);
|
||||||
|
|
||||||
|
const { commentRoutes } = await import('./routes.js');
|
||||||
|
const app = Fastify({ logger: false });
|
||||||
|
await app.register(commentRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
|
const res = await app.inject({ method: 'DELETE', url: '/api/items/item_1/comments/missing' });
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(404);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user