Skip to content

Commit 39bac1b

Browse files
authored
Merge pull request #671 from Kaylahray/test-coverage
test(gameplay): cover core streak, puzzle, user, quest, progress, and category providers
2 parents c7130c2 + e6e7b0f commit 39bac1b

17 files changed

Lines changed: 2288 additions & 5 deletions
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { getRepositoryToken } from '@nestjs/typeorm';
3+
import {
4+
ConflictException,
5+
InternalServerErrorException,
6+
} from '@nestjs/common';
7+
import { Repository } from 'typeorm';
8+
import { CategoriesService } from './categories.service';
9+
import { Category } from '../entities/category.entity';
10+
import { CreateCategoryDto } from '../dtos/create-category.dto';
11+
12+
function makeCategory(overrides?: Partial<Category>): Category {
13+
return {
14+
id: 'cat-1',
15+
name: 'Logic',
16+
description: 'Logic puzzles',
17+
isActive: true,
18+
createdAt: new Date(),
19+
puzzles: [],
20+
...overrides,
21+
};
22+
}
23+
24+
describe('CategoriesService', () => {
25+
let service: CategoriesService;
26+
let categoryRepo: jest.Mocked<Repository<Category>>;
27+
28+
beforeEach(async () => {
29+
const mockRepo = {
30+
find: jest.fn(),
31+
findOne: jest.fn(),
32+
create: jest.fn(),
33+
save: jest.fn(),
34+
update: jest.fn(),
35+
delete: jest.fn(),
36+
};
37+
38+
const module: TestingModule = await Test.createTestingModule({
39+
providers: [
40+
CategoriesService,
41+
{ provide: getRepositoryToken(Category), useValue: mockRepo },
42+
],
43+
}).compile();
44+
45+
service = module.get(CategoriesService);
46+
categoryRepo = module.get(getRepositoryToken(Category));
47+
});
48+
49+
afterEach(() => {
50+
jest.clearAllMocks();
51+
});
52+
53+
it('lists categories ordered by name ASC', async () => {
54+
const rows = [makeCategory()];
55+
categoryRepo.find.mockResolvedValue(rows);
56+
57+
const result = await service.findAll();
58+
59+
expect(categoryRepo.find).toHaveBeenCalledWith({ order: { name: 'ASC' } });
60+
expect(result).toBe(rows);
61+
});
62+
63+
it('finds a category by id and returns null when missing', async () => {
64+
categoryRepo.findOne.mockResolvedValueOnce(makeCategory());
65+
await expect(service.findOne('cat-1')).resolves.toMatchObject({
66+
id: 'cat-1',
67+
});
68+
69+
categoryRepo.findOne.mockResolvedValueOnce(null);
70+
await expect(service.findOne('missing')).resolves.toBeNull();
71+
});
72+
73+
it('returns only active categories', async () => {
74+
const active = [makeCategory()];
75+
categoryRepo.find.mockResolvedValue(active);
76+
77+
await expect(service.findActive()).resolves.toBe(active);
78+
expect(categoryRepo.find).toHaveBeenCalledWith({
79+
where: { isActive: true },
80+
});
81+
});
82+
83+
describe('create', () => {
84+
const dto: CreateCategoryDto = { name: 'Logic', description: 'puzzles' };
85+
86+
it('creates and saves a new category', async () => {
87+
const created = makeCategory();
88+
categoryRepo.findOne.mockResolvedValue(null);
89+
categoryRepo.create.mockReturnValue(created);
90+
categoryRepo.save.mockResolvedValue(created);
91+
92+
const result = await service.create(dto);
93+
94+
expect(categoryRepo.findOne).toHaveBeenCalledWith({
95+
where: { name: 'Logic' },
96+
});
97+
expect(categoryRepo.create).toHaveBeenCalledWith(dto);
98+
expect(result).toBe(created);
99+
});
100+
101+
it('throws ConflictException when the name already exists', async () => {
102+
categoryRepo.findOne.mockResolvedValue(makeCategory());
103+
104+
await expect(service.create(dto)).rejects.toThrow(ConflictException);
105+
expect(categoryRepo.create).not.toHaveBeenCalled();
106+
});
107+
108+
it('maps a unique-constraint race (Postgres 23505) to ConflictException', async () => {
109+
categoryRepo.findOne.mockResolvedValue(null);
110+
categoryRepo.create.mockReturnValue(makeCategory());
111+
const uniqueError = Object.assign(new Error('duplicate'), {
112+
code: '23505',
113+
});
114+
categoryRepo.save.mockRejectedValue(uniqueError);
115+
116+
await expect(service.create(dto)).rejects.toThrow(ConflictException);
117+
});
118+
119+
it('throws InternalServerErrorException for other save failures', async () => {
120+
categoryRepo.findOne.mockResolvedValue(null);
121+
categoryRepo.create.mockReturnValue(makeCategory());
122+
categoryRepo.save.mockRejectedValue(new Error('disk full'));
123+
124+
await expect(service.create(dto)).rejects.toThrow(
125+
InternalServerErrorException,
126+
);
127+
});
128+
});
129+
130+
it('updates a category and returns the refreshed row', async () => {
131+
const updated = makeCategory({ name: 'Reasoning' });
132+
categoryRepo.update.mockResolvedValue({} as any);
133+
categoryRepo.findOne.mockResolvedValue(updated);
134+
135+
const result = await service.update('cat-1', { name: 'Reasoning' });
136+
137+
expect(categoryRepo.update).toHaveBeenCalledWith('cat-1', {
138+
name: 'Reasoning',
139+
});
140+
expect(result).toBe(updated);
141+
});
142+
143+
it('deletes a category by id', async () => {
144+
categoryRepo.delete.mockResolvedValue({} as any);
145+
146+
await service.remove('cat-1');
147+
148+
expect(categoryRepo.delete).toHaveBeenCalledWith('cat-1');
149+
});
150+
});

0 commit comments

Comments
 (0)