Add interface design references and principles; implement sport service tests

- Introduced critique, example, principles, and validation documents for interface design.
- Enhanced backend service with coverage threshold and fixed variable declaration in sport service.
- Added comprehensive tests for create, update, and list sports functionalities.
This commit is contained in:
Jose Selesan
2026-04-24 10:40:25 -03:00
parent 21d0c27f4d
commit c9ecdd2c77
10 changed files with 1135 additions and 1 deletions

View File

@@ -0,0 +1,199 @@
import { beforeEach, expect, mock, test } from 'bun:test';
import { createPrismaMock } from 'bun-mock-prisma';
import type { PrismaClient } from '@/generated/prisma/client';
import type { PrismaClientMock } from 'bun-mock-prisma';
const prismaMock = createPrismaMock<PrismaClient>() as PrismaClientMock<PrismaClient>;
const uuidV7Mock = mock(() => '00000000-0000-4000-8000-000000000001');
mock.module('@/lib/prisma', () => ({
__esModule: true,
db: {
sport: prismaMock.sport,
},
}));
mock.module('uuid', () => ({
__esModule: true,
v7: uuidV7Mock,
}));
const { createSport, getSportById, listSports, updateSport } = await import(
'@/modules/sport/services/sport.service'
);
beforeEach(() => {
prismaMock._reset();
mock.clearAllMocks();
});
test('listSports returns sports ordered by name', async () => {
const sports = [
{
id: 'sport-2',
name: 'Tenis',
slug: 'tenis',
isActive: true,
createdAt: new Date('2026-04-22T00:00:00.000Z'),
updatedAt: new Date('2026-04-22T00:00:00.000Z'),
},
{
id: 'sport-1',
name: 'Básquet',
slug: 'basquet',
isActive: true,
createdAt: new Date('2026-04-21T00:00:00.000Z'),
updatedAt: new Date('2026-04-21T00:00:00.000Z'),
},
];
prismaMock.sport.findMany.mockResolvedValue(sports as never);
const result = await listSports();
expect(prismaMock.sport.findMany).toHaveBeenCalledWith({
orderBy: { name: 'asc' },
});
expect(result).toEqual({
ok: true,
value: sports,
});
});
test('getSportById returns the requested sport', async () => {
const sport = {
id: 'sport-1',
name: 'Tenis',
slug: 'tenis',
isActive: true,
createdAt: new Date('2026-04-22T00:00:00.000Z'),
updatedAt: new Date('2026-04-22T00:00:00.000Z'),
};
prismaMock.sport.findUnique.mockResolvedValue(sport as never);
const result = await getSportById('sport-1');
expect(prismaMock.sport.findUnique).toHaveBeenCalledWith({
where: { id: 'sport-1' },
});
expect(result).toEqual(sport);
});
test('createSport trims the name and creates a unique slug', async () => {
const createdSport = {
id: '00000000-0000-4000-8000-000000000001',
name: 'Fútbol 7!!',
slug: 'futbol-7',
isActive: true,
createdAt: new Date('2026-04-23T00:00:00.000Z'),
updatedAt: new Date('2026-04-23T00:00:00.000Z'),
};
prismaMock.sport.findFirst.mockResolvedValue(null as never);
prismaMock.sport.count.mockResolvedValue(0 as never);
prismaMock.sport.create.mockResolvedValue(createdSport as never);
const result = await createSport({ name: ' Fútbol 7!! ' });
expect(uuidV7Mock).toHaveBeenCalledTimes(1);
expect(prismaMock.sport.findFirst).toHaveBeenCalledWith({
where: { slug: 'futbol-7' },
select: { id: true },
});
expect(prismaMock.sport.count).toHaveBeenCalledWith({
where: { slug: 'futbol-7' },
});
expect(prismaMock.sport.create).toHaveBeenCalledWith({
data: {
id: '00000000-0000-4000-8000-000000000001',
name: 'Fútbol 7!!',
slug: 'futbol-7',
isActive: true,
},
});
expect(result).toEqual({
ok: true,
value: createdSport,
});
});
test('createSport retries with a numeric suffix when the slug already exists', async () => {
const createdSport = {
id: '00000000-0000-4000-8000-000000000001',
name: 'Tenis',
slug: 'tenis-2',
isActive: true,
createdAt: new Date('2026-04-23T00:00:00.000Z'),
updatedAt: new Date('2026-04-23T00:00:00.000Z'),
};
prismaMock.sport.findFirst
.mockResolvedValueOnce({ id: 'existing-sport' } as never)
.mockResolvedValueOnce(null as never);
prismaMock.sport.count.mockResolvedValue(0 as never);
prismaMock.sport.create.mockResolvedValue(createdSport as never);
const result = await createSport({ name: 'Tenis' });
expect(prismaMock.sport.findFirst).toHaveBeenNthCalledWith(1, {
where: { slug: 'tenis' },
select: { id: true },
});
expect(prismaMock.sport.findFirst).toHaveBeenNthCalledWith(2, {
where: { slug: 'tenis-2' },
select: { id: true },
});
expect(prismaMock.sport.create).toHaveBeenCalledWith({
data: {
id: '00000000-0000-4000-8000-000000000001',
name: 'Tenis',
slug: 'tenis-2',
isActive: true,
},
});
expect(result).toEqual({
ok: true,
value: createdSport,
});
});
test('updateSport updates the name, slug and active flag', async () => {
const updatedSport = {
id: 'sport-1',
name: 'Básquet 3x3',
slug: 'basquet-3x3',
isActive: false,
createdAt: new Date('2026-04-21T00:00:00.000Z'),
updatedAt: new Date('2026-04-23T00:00:00.000Z'),
};
prismaMock.sport.findFirst.mockResolvedValue(null as never);
prismaMock.sport.update.mockResolvedValue(updatedSport as never);
const result = await updateSport('sport-1', {
name: 'Básquet 3x3',
isActive: false,
});
expect(prismaMock.sport.findFirst).toHaveBeenCalledWith({
where: {
slug: 'basquet-3x3',
id: {
not: 'sport-1',
},
},
select: { id: true },
});
expect(prismaMock.sport.update).toHaveBeenCalledWith({
where: { id: 'sport-1' },
data: {
name: 'Básquet 3x3',
slug: 'basquet-3x3',
isActive: false,
},
});
expect(result).toEqual(updatedSport);
});