test(platform-service): add comments route-level tests

This commit is contained in:
saravanakumardb1 2026-02-16 12:43:56 -08:00
parent 3e1647ae96
commit e4ee23aab4

View 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);
});
});