feat(api): add RoutesModule with GET/POST/DELETE /routes endpoints

Implements RoutesService and RoutesController for SyncRoute CRUD, wires
GroupsModule and RoutesModule into AppModule; 11 new tests, all 31 pass.
This commit is contained in:
2026-05-28 01:07:15 +05:30
parent f4a40b573e
commit 6b4920ce41
6 changed files with 221 additions and 0 deletions
@@ -0,0 +1,93 @@
import { Test, TestingModule } from '@nestjs/testing';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { RoutesService } from './routes.service';
import { PrismaService } from '../../prisma/prisma.service';
const mockRoute = {
id: 'rt_1',
sourceGroupId: 'grp_1',
targetGroupId: 'grp_2',
isActive: true,
createdAt: new Date(),
sourceGroup: { name: 'Alpha' },
targetGroup: { name: 'Beta' },
};
describe('RoutesService', () => {
let service: RoutesService;
const mockPrisma = {
syncRoute: {
findMany: jest.fn().mockResolvedValue([mockRoute]),
create: jest.fn().mockResolvedValue(mockRoute),
delete: jest.fn().mockResolvedValue(mockRoute),
},
};
beforeEach(async () => {
jest.clearAllMocks();
const module: TestingModule = await Test.createTestingModule({
providers: [
RoutesService,
{ provide: PrismaService, useValue: mockPrisma },
],
}).compile();
service = module.get<RoutesService>(RoutesService);
});
describe('list', () => {
it('returns all routes with group names', async () => {
const result = await service.list();
expect(result).toHaveLength(1);
expect(result[0].sourceGroup.name).toBe('Alpha');
expect(mockPrisma.syncRoute.findMany).toHaveBeenCalledWith({
where: undefined,
include: {
sourceGroup: { select: { name: true } },
targetGroup: { select: { name: true } },
},
orderBy: { createdAt: 'desc' },
});
});
it('filters by sourceGroupId when provided', async () => {
await service.list('grp_1');
expect(mockPrisma.syncRoute.findMany).toHaveBeenCalledWith(
expect.objectContaining({ where: { sourceGroupId: 'grp_1' } }),
);
});
});
describe('create', () => {
it('creates a route and returns it with group names', async () => {
const result = await service.create('grp_1', 'grp_2');
expect(result).toEqual(mockRoute);
expect(mockPrisma.syncRoute.create).toHaveBeenCalledWith({
data: { sourceGroupId: 'grp_1', targetGroupId: 'grp_2' },
include: {
sourceGroup: { select: { name: true } },
targetGroup: { select: { name: true } },
},
});
});
it('throws BadRequestException when sourceGroupId is empty', async () => {
await expect(service.create('', 'grp_2')).rejects.toThrow(BadRequestException);
});
it('throws BadRequestException when targetGroupId is empty', async () => {
await expect(service.create('grp_1', '')).rejects.toThrow(BadRequestException);
});
});
describe('remove', () => {
it('deletes a route by id', async () => {
await service.remove('rt_1');
expect(mockPrisma.syncRoute.delete).toHaveBeenCalledWith({ where: { id: 'rt_1' } });
});
it('throws NotFoundException when route does not exist (Prisma P2025)', async () => {
mockPrisma.syncRoute.delete.mockRejectedValueOnce({ code: 'P2025' });
await expect(service.remove('bad_id')).rejects.toThrow(NotFoundException);
});
});
});