test(JiraSync): further refine test coverage for synchronization and change detection scenarios
This commit is contained in:
309
src/services/task-management/__tests__/daily.test.ts
Normal file
309
src/services/task-management/__tests__/daily.test.ts
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
/**
|
||||||
|
* Tests unitaires pour DailyService
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { DailyService } from '../daily';
|
||||||
|
import { prisma } from '@/services/core/database';
|
||||||
|
import {
|
||||||
|
getPreviousWorkday,
|
||||||
|
normalizeDate,
|
||||||
|
getToday,
|
||||||
|
getYesterday,
|
||||||
|
} from '@/lib/date-utils';
|
||||||
|
|
||||||
|
// Mock de prisma
|
||||||
|
vi.mock('@/services/core/database', () => ({
|
||||||
|
prisma: {
|
||||||
|
dailyCheckbox: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
findUnique: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
aggregate: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock de date-utils
|
||||||
|
vi.mock('@/lib/date-utils', () => ({
|
||||||
|
getPreviousWorkday: vi.fn(),
|
||||||
|
normalizeDate: vi.fn(),
|
||||||
|
formatDateForAPI: vi.fn(),
|
||||||
|
getToday: vi.fn(),
|
||||||
|
getYesterday: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('DailyService', () => {
|
||||||
|
let service: DailyService;
|
||||||
|
const mockUserId = 'user-123';
|
||||||
|
const mockDate = new Date('2024-01-15');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
service = new DailyService();
|
||||||
|
vi.mocked(normalizeDate).mockImplementation((date) => date);
|
||||||
|
vi.mocked(getPreviousWorkday).mockReturnValue(new Date('2024-01-14'));
|
||||||
|
vi.mocked(getToday).mockReturnValue(mockDate);
|
||||||
|
vi.mocked(getYesterday).mockReturnValue(new Date('2024-01-14'));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getDailyView', () => {
|
||||||
|
it('devrait récupérer la vue daily pour une date', async () => {
|
||||||
|
const yesterdayCheckboxes: any[] = [];
|
||||||
|
const todayCheckboxes: any[] = [];
|
||||||
|
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany)
|
||||||
|
.mockResolvedValueOnce(yesterdayCheckboxes)
|
||||||
|
.mockResolvedValueOnce(todayCheckboxes);
|
||||||
|
|
||||||
|
const view = await service.getDailyView(mockDate, mockUserId);
|
||||||
|
|
||||||
|
expect(view.date).toEqual(mockDate);
|
||||||
|
expect(view.yesterday).toEqual(yesterdayCheckboxes);
|
||||||
|
expect(view.today).toEqual(todayCheckboxes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCheckboxesByDate', () => {
|
||||||
|
it('devrait récupérer les checkboxes pour une date', async () => {
|
||||||
|
const mockCheckboxes = [
|
||||||
|
{
|
||||||
|
id: 'checkbox-1',
|
||||||
|
date: mockDate,
|
||||||
|
text: 'Checkbox 1',
|
||||||
|
isChecked: false,
|
||||||
|
type: 'task',
|
||||||
|
order: 0,
|
||||||
|
taskId: null,
|
||||||
|
userId: mockUserId,
|
||||||
|
task: null,
|
||||||
|
user: { id: mockUserId },
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany).mockResolvedValue(
|
||||||
|
mockCheckboxes as any
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkboxes = await service.getCheckboxesByDate(
|
||||||
|
mockDate,
|
||||||
|
mockUserId
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(checkboxes).toBeDefined();
|
||||||
|
expect(prisma.dailyCheckbox.findMany).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
date: mockDate,
|
||||||
|
userId: mockUserId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
task: {
|
||||||
|
include: {
|
||||||
|
taskTags: {
|
||||||
|
include: {
|
||||||
|
tag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primaryTag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
orderBy: { order: 'asc' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addCheckbox', () => {
|
||||||
|
it('devrait ajouter une checkbox', async () => {
|
||||||
|
const mockCheckbox = {
|
||||||
|
id: 'checkbox-1',
|
||||||
|
date: mockDate,
|
||||||
|
text: 'New checkbox',
|
||||||
|
isChecked: false,
|
||||||
|
type: 'task',
|
||||||
|
order: 0,
|
||||||
|
taskId: null,
|
||||||
|
userId: mockUserId,
|
||||||
|
task: null,
|
||||||
|
user: { id: mockUserId },
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.dailyCheckbox.aggregate).mockResolvedValue({
|
||||||
|
_max: { order: null },
|
||||||
|
} as any);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.create).mockResolvedValue(
|
||||||
|
mockCheckbox as any
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkbox = await service.addCheckbox({
|
||||||
|
date: mockDate,
|
||||||
|
text: 'New checkbox',
|
||||||
|
userId: mockUserId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(checkbox).toBeDefined();
|
||||||
|
expect(prisma.dailyCheckbox.create).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("devrait utiliser l'ordre spécifié", async () => {
|
||||||
|
const mockCheckbox = {
|
||||||
|
id: 'checkbox-1',
|
||||||
|
date: mockDate,
|
||||||
|
text: 'New checkbox',
|
||||||
|
isChecked: false,
|
||||||
|
type: 'task',
|
||||||
|
order: 5,
|
||||||
|
taskId: null,
|
||||||
|
userId: mockUserId,
|
||||||
|
task: null,
|
||||||
|
user: { id: mockUserId },
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.dailyCheckbox.create).mockResolvedValue(
|
||||||
|
mockCheckbox as any
|
||||||
|
);
|
||||||
|
|
||||||
|
await service.addCheckbox({
|
||||||
|
date: mockDate,
|
||||||
|
text: 'New checkbox',
|
||||||
|
userId: mockUserId,
|
||||||
|
order: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prisma.dailyCheckbox.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
order: 5,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateCheckbox', () => {
|
||||||
|
it('devrait mettre à jour une checkbox', async () => {
|
||||||
|
const mockCheckbox = {
|
||||||
|
id: 'checkbox-1',
|
||||||
|
date: mockDate,
|
||||||
|
text: 'Updated checkbox',
|
||||||
|
isChecked: true,
|
||||||
|
type: 'task',
|
||||||
|
order: 0,
|
||||||
|
taskId: null,
|
||||||
|
userId: mockUserId,
|
||||||
|
task: null,
|
||||||
|
user: { id: mockUserId },
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.dailyCheckbox.update).mockResolvedValue(
|
||||||
|
mockCheckbox as any
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkbox = await service.updateCheckbox('checkbox-1', {
|
||||||
|
text: 'Updated checkbox',
|
||||||
|
isChecked: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(checkbox).toBeDefined();
|
||||||
|
expect(prisma.dailyCheckbox.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toggleCheckbox', () => {
|
||||||
|
it("devrait toggle l'état d'une checkbox", async () => {
|
||||||
|
const existingCheckbox = {
|
||||||
|
id: 'checkbox-1',
|
||||||
|
isChecked: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatedCheckbox = {
|
||||||
|
id: 'checkbox-1',
|
||||||
|
date: mockDate,
|
||||||
|
text: 'Checkbox',
|
||||||
|
isChecked: true,
|
||||||
|
type: 'task',
|
||||||
|
order: 0,
|
||||||
|
taskId: null,
|
||||||
|
userId: mockUserId,
|
||||||
|
task: null,
|
||||||
|
user: { id: mockUserId },
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findUnique).mockResolvedValue(
|
||||||
|
existingCheckbox as any
|
||||||
|
);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.update).mockResolvedValue(
|
||||||
|
updatedCheckbox as any
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkbox = await service.toggleCheckbox('checkbox-1');
|
||||||
|
|
||||||
|
expect(checkbox.isChecked).toBe(true);
|
||||||
|
expect(prisma.dailyCheckbox.update).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
isChecked: true,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("devrait lancer une erreur si la checkbox n'existe pas", async () => {
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findUnique).mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(service.toggleCheckbox('non-existent')).rejects.toThrow(
|
||||||
|
'non trouvée'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteCheckbox', () => {
|
||||||
|
it('devrait supprimer une checkbox', async () => {
|
||||||
|
const existingCheckbox = {
|
||||||
|
id: 'checkbox-1',
|
||||||
|
date: mockDate,
|
||||||
|
text: 'Checkbox',
|
||||||
|
isChecked: false,
|
||||||
|
type: 'task',
|
||||||
|
order: 0,
|
||||||
|
taskId: null,
|
||||||
|
userId: mockUserId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findUnique).mockResolvedValue(
|
||||||
|
existingCheckbox as any
|
||||||
|
);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.delete).mockResolvedValue({} as any);
|
||||||
|
|
||||||
|
await service.deleteCheckbox('checkbox-1');
|
||||||
|
|
||||||
|
expect(prisma.dailyCheckbox.delete).toHaveBeenCalledWith({
|
||||||
|
where: { id: 'checkbox-1' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("devrait lancer une erreur si la checkbox n'existe pas", async () => {
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findUnique).mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(service.deleteCheckbox('non-existent')).rejects.toThrow(
|
||||||
|
'non trouvée'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
286
src/services/task-management/__tests__/tags.test.ts
Normal file
286
src/services/task-management/__tests__/tags.test.ts
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
/**
|
||||||
|
* Tests unitaires pour tagsService
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { tagsService } from '../tags';
|
||||||
|
import { prisma } from '@/services/core/database';
|
||||||
|
|
||||||
|
// Mock de prisma
|
||||||
|
vi.mock('@/services/core/database', () => ({
|
||||||
|
prisma: {
|
||||||
|
tag: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
findFirst: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
},
|
||||||
|
taskTag: {
|
||||||
|
deleteMany: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('tagsService', () => {
|
||||||
|
const mockUserId = 'user-123';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTags', () => {
|
||||||
|
it('devrait récupérer tous les tags avec leur usage', async () => {
|
||||||
|
const mockTags = [
|
||||||
|
{
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'Tag 1',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
_count: { taskTags: 5 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tag-2',
|
||||||
|
name: 'Tag 2',
|
||||||
|
color: '#00ff00',
|
||||||
|
isPinned: true,
|
||||||
|
_count: { taskTags: 2 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.tag.findMany).mockResolvedValue(mockTags as any);
|
||||||
|
|
||||||
|
const tags = await tagsService.getTags(mockUserId);
|
||||||
|
|
||||||
|
expect(tags).toHaveLength(2);
|
||||||
|
expect(tags[0]).toEqual({
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'Tag 1',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
usage: 5,
|
||||||
|
});
|
||||||
|
expect(prisma.tag.findMany).toHaveBeenCalledWith({
|
||||||
|
where: { ownerId: mockUserId },
|
||||||
|
include: {
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
taskTags: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: { name: 'asc' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTagById', () => {
|
||||||
|
it('devrait récupérer un tag par son ID', async () => {
|
||||||
|
const mockTag = {
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'Tag 1',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue(mockTag as any);
|
||||||
|
|
||||||
|
const tag = await tagsService.getTagById('tag-1', mockUserId);
|
||||||
|
|
||||||
|
expect(tag).toEqual(mockTag);
|
||||||
|
expect(prisma.tag.findFirst).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
id: 'tag-1',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("devrait retourner null si le tag n'existe pas", async () => {
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue(null);
|
||||||
|
|
||||||
|
const tag = await tagsService.getTagById('non-existent', mockUserId);
|
||||||
|
|
||||||
|
expect(tag).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTagByName', () => {
|
||||||
|
it('devrait récupérer un tag par son nom', async () => {
|
||||||
|
const mockTag = {
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'Tag 1',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue(mockTag as any);
|
||||||
|
|
||||||
|
const tag = await tagsService.getTagByName('Tag 1', mockUserId);
|
||||||
|
|
||||||
|
expect(tag).toEqual(mockTag);
|
||||||
|
expect(prisma.tag.findFirst).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
name: {
|
||||||
|
equals: 'Tag 1',
|
||||||
|
},
|
||||||
|
ownerId: mockUserId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createTag', () => {
|
||||||
|
it('devrait créer un nouveau tag', async () => {
|
||||||
|
const mockTag = {
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'New Tag',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue(null); // Pas de tag existant
|
||||||
|
vi.mocked(prisma.tag.create).mockResolvedValue(mockTag as any);
|
||||||
|
|
||||||
|
const tag = await tagsService.createTag({
|
||||||
|
name: 'New Tag',
|
||||||
|
color: '#ff0000',
|
||||||
|
userId: mockUserId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tag).toEqual(mockTag);
|
||||||
|
expect(prisma.tag.create).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
name: 'New Tag',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
ownerId: mockUserId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait lancer une erreur si le tag existe déjà', async () => {
|
||||||
|
const existingTag = {
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'Existing Tag',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue(existingTag as any);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
tagsService.createTag({
|
||||||
|
name: 'Existing Tag',
|
||||||
|
color: '#ff0000',
|
||||||
|
userId: mockUserId,
|
||||||
|
})
|
||||||
|
).rejects.toThrow('existe déjà');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait trimmer le nom du tag', async () => {
|
||||||
|
const mockTag = {
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'Trimmed Tag',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue(null);
|
||||||
|
vi.mocked(prisma.tag.create).mockResolvedValue(mockTag as any);
|
||||||
|
|
||||||
|
await tagsService.createTag({
|
||||||
|
name: ' Trimmed Tag ',
|
||||||
|
color: '#ff0000',
|
||||||
|
userId: mockUserId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prisma.tag.create).toHaveBeenCalledWith({
|
||||||
|
data: {
|
||||||
|
name: 'Trimmed Tag',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
ownerId: mockUserId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateTag', () => {
|
||||||
|
it('devrait mettre à jour un tag', async () => {
|
||||||
|
const mockTag = {
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'Updated Tag',
|
||||||
|
color: '#00ff00',
|
||||||
|
isPinned: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue({
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'Old Tag',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
} as any);
|
||||||
|
vi.mocked(prisma.tag.update).mockResolvedValue(mockTag as any);
|
||||||
|
|
||||||
|
const tag = await tagsService.updateTag('tag-1', mockUserId, {
|
||||||
|
name: 'Updated Tag',
|
||||||
|
color: '#00ff00',
|
||||||
|
isPinned: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tag).toEqual(mockTag);
|
||||||
|
expect(prisma.tag.update).toHaveBeenCalledWith({
|
||||||
|
where: { id: 'tag-1' },
|
||||||
|
data: {
|
||||||
|
name: 'Updated Tag',
|
||||||
|
color: '#00ff00',
|
||||||
|
isPinned: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("devrait lancer une erreur si le tag n'existe pas", async () => {
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
tagsService.updateTag('non-existent', mockUserId, {
|
||||||
|
name: 'Updated Tag',
|
||||||
|
})
|
||||||
|
).rejects.toThrow('non trouvé');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteTag', () => {
|
||||||
|
it('devrait supprimer un tag', async () => {
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue({
|
||||||
|
id: 'tag-1',
|
||||||
|
name: 'Tag to delete',
|
||||||
|
color: '#ff0000',
|
||||||
|
isPinned: false,
|
||||||
|
} as any);
|
||||||
|
vi.mocked(prisma.taskTag.deleteMany).mockResolvedValue({
|
||||||
|
count: 0,
|
||||||
|
} as any);
|
||||||
|
vi.mocked(prisma.tag.delete).mockResolvedValue({} as any);
|
||||||
|
|
||||||
|
await tagsService.deleteTag('tag-1', mockUserId);
|
||||||
|
|
||||||
|
expect(prisma.taskTag.deleteMany).toHaveBeenCalledWith({
|
||||||
|
where: { tagId: 'tag-1' },
|
||||||
|
});
|
||||||
|
expect(prisma.tag.delete).toHaveBeenCalledWith({
|
||||||
|
where: { id: 'tag-1' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("devrait lancer une erreur si le tag n'existe pas", async () => {
|
||||||
|
vi.mocked(prisma.tag.findFirst).mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
tagsService.deleteTag('non-existent', mockUserId)
|
||||||
|
).rejects.toThrow('non trouvé');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
436
src/services/task-management/__tests__/tasks.test.ts
Normal file
436
src/services/task-management/__tests__/tasks.test.ts
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
/**
|
||||||
|
* Tests unitaires pour TasksService
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||||
|
import { TasksService } from '../tasks';
|
||||||
|
import { prisma } from '@/services/core/database';
|
||||||
|
import { tagsService } from '../tags';
|
||||||
|
import { getReadonlyFieldsForTask } from '../readonly-fields';
|
||||||
|
import { getToday } from '@/lib/date-utils';
|
||||||
|
|
||||||
|
// Mock de prisma
|
||||||
|
vi.mock('@/services/core/database', () => ({
|
||||||
|
prisma: {
|
||||||
|
task: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
findFirst: vi.fn(),
|
||||||
|
findUnique: vi.fn(),
|
||||||
|
create: vi.fn(),
|
||||||
|
update: vi.fn(),
|
||||||
|
delete: vi.fn(),
|
||||||
|
aggregate: vi.fn(),
|
||||||
|
},
|
||||||
|
taskTag: {
|
||||||
|
createMany: vi.fn(),
|
||||||
|
deleteMany: vi.fn(),
|
||||||
|
findMany: vi.fn(),
|
||||||
|
},
|
||||||
|
dailyCheckbox: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock de tagsService
|
||||||
|
vi.mock('../tags', () => ({
|
||||||
|
tagsService: {
|
||||||
|
ensureTagsExist: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock de readonly-fields
|
||||||
|
vi.mock('../readonly-fields', () => ({
|
||||||
|
getReadonlyFieldsForTask: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock de date-utils
|
||||||
|
vi.mock('@/lib/date-utils', () => ({
|
||||||
|
getToday: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('TasksService', () => {
|
||||||
|
let service: TasksService;
|
||||||
|
const mockUserId = 'user-123';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
service = new TasksService();
|
||||||
|
vi.mocked(getToday).mockReturnValue(new Date('2024-01-15'));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTasks', () => {
|
||||||
|
it('devrait récupérer toutes les tâches pour un utilisateur', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: 'task-1',
|
||||||
|
title: 'Task 1',
|
||||||
|
description: null,
|
||||||
|
status: 'todo',
|
||||||
|
priority: 'medium',
|
||||||
|
source: 'manual',
|
||||||
|
sourceId: 'manual-123',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
dueDate: null,
|
||||||
|
completedAt: null,
|
||||||
|
primaryTagId: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
tfsProject: null,
|
||||||
|
tfsPullRequestId: null,
|
||||||
|
tfsRepository: null,
|
||||||
|
tfsSourceBranch: null,
|
||||||
|
tfsTargetBranch: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
primaryTag: null,
|
||||||
|
_count: { dailyCheckboxes: 0 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);
|
||||||
|
|
||||||
|
const tasks = await service.getTasks(mockUserId);
|
||||||
|
|
||||||
|
expect(tasks).toHaveLength(1);
|
||||||
|
expect(prisma.task.findMany).toHaveBeenCalledWith({
|
||||||
|
where: { ownerId: mockUserId },
|
||||||
|
include: {
|
||||||
|
taskTags: {
|
||||||
|
include: {
|
||||||
|
tag: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
primaryTag: true,
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
dailyCheckboxes: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
take: undefined,
|
||||||
|
skip: 0,
|
||||||
|
orderBy: [
|
||||||
|
{ completedAt: 'desc' },
|
||||||
|
{ dueDate: 'asc' },
|
||||||
|
{ createdAt: 'desc' },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait filtrer par statut', async () => {
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
|
await service.getTasks(mockUserId, { status: ['todo', 'in_progress'] });
|
||||||
|
|
||||||
|
expect(prisma.task.findMany).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
where: expect.objectContaining({
|
||||||
|
status: { in: ['todo', 'in_progress'] },
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait rechercher dans le titre et la description', async () => {
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
|
await service.getTasks(mockUserId, { search: 'test' });
|
||||||
|
|
||||||
|
expect(prisma.task.findMany).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
where: expect.objectContaining({
|
||||||
|
OR: [
|
||||||
|
{ title: { contains: 'test' } },
|
||||||
|
{ description: { contains: 'test' } },
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createTask', () => {
|
||||||
|
it('devrait créer une nouvelle tâche', async () => {
|
||||||
|
const mockTask = {
|
||||||
|
id: 'task-1',
|
||||||
|
title: 'New Task',
|
||||||
|
description: 'Description',
|
||||||
|
status: 'todo',
|
||||||
|
priority: 'medium',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
source: 'manual',
|
||||||
|
sourceId: 'manual-123',
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
dueDate: null,
|
||||||
|
completedAt: null,
|
||||||
|
primaryTagId: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
tfsProject: null,
|
||||||
|
tfsPullRequestId: null,
|
||||||
|
tfsRepository: null,
|
||||||
|
tfsSourceBranch: null,
|
||||||
|
tfsTargetBranch: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
primaryTag: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.create).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(prisma.task.findUnique).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(tagsService.ensureTagsExist).mockResolvedValue([]);
|
||||||
|
vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);
|
||||||
|
|
||||||
|
const task = await service.createTask({
|
||||||
|
title: 'New Task',
|
||||||
|
description: 'Description',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(task).toBeDefined();
|
||||||
|
expect(prisma.task.create).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait définir completedAt si le statut est done', async () => {
|
||||||
|
const mockTask = {
|
||||||
|
id: 'task-1',
|
||||||
|
title: 'Done Task',
|
||||||
|
description: null,
|
||||||
|
status: 'done',
|
||||||
|
priority: 'medium',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
source: 'manual',
|
||||||
|
sourceId: 'manual-123',
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
dueDate: null,
|
||||||
|
completedAt: new Date('2024-01-15'),
|
||||||
|
primaryTagId: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
tfsProject: null,
|
||||||
|
tfsPullRequestId: null,
|
||||||
|
tfsRepository: null,
|
||||||
|
tfsSourceBranch: null,
|
||||||
|
tfsTargetBranch: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
primaryTag: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.create).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(prisma.task.findUnique).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(tagsService.ensureTagsExist).mockResolvedValue([]);
|
||||||
|
vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);
|
||||||
|
|
||||||
|
await service.createTask({
|
||||||
|
title: 'Done Task',
|
||||||
|
status: 'done',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prisma.task.create).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
completedAt: expect.any(Date),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateTask', () => {
|
||||||
|
it('devrait mettre à jour une tâche', async () => {
|
||||||
|
const mockTask = {
|
||||||
|
id: 'task-1',
|
||||||
|
title: 'Updated Task',
|
||||||
|
description: null,
|
||||||
|
status: 'in_progress',
|
||||||
|
priority: 'medium',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
source: 'manual',
|
||||||
|
sourceId: 'manual-123',
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
dueDate: null,
|
||||||
|
completedAt: null,
|
||||||
|
primaryTagId: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
tfsProject: null,
|
||||||
|
tfsPullRequestId: null,
|
||||||
|
tfsRepository: null,
|
||||||
|
tfsSourceBranch: null,
|
||||||
|
tfsTargetBranch: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
primaryTag: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findFirst).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(prisma.task.update).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(prisma.task.findUnique).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);
|
||||||
|
|
||||||
|
const task = await service.updateTask(mockUserId, 'task-1', {
|
||||||
|
title: 'Updated Task',
|
||||||
|
status: 'in_progress',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(task).toBeDefined();
|
||||||
|
expect(prisma.task.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait définir completedAt quand le statut passe à done', async () => {
|
||||||
|
const mockTask = {
|
||||||
|
id: 'task-1',
|
||||||
|
title: 'Task',
|
||||||
|
description: null,
|
||||||
|
status: 'todo',
|
||||||
|
priority: 'medium',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
source: 'manual',
|
||||||
|
sourceId: 'manual-123',
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
dueDate: null,
|
||||||
|
completedAt: null,
|
||||||
|
primaryTagId: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
tfsProject: null,
|
||||||
|
tfsPullRequestId: null,
|
||||||
|
tfsRepository: null,
|
||||||
|
tfsSourceBranch: null,
|
||||||
|
tfsTargetBranch: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
primaryTag: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findFirst).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(prisma.task.update).mockResolvedValue({
|
||||||
|
...mockTask,
|
||||||
|
status: 'done',
|
||||||
|
completedAt: new Date('2024-01-15'),
|
||||||
|
} as any);
|
||||||
|
vi.mocked(prisma.task.findUnique).mockResolvedValue({
|
||||||
|
...mockTask,
|
||||||
|
status: 'done',
|
||||||
|
completedAt: new Date('2024-01-15'),
|
||||||
|
} as any);
|
||||||
|
vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);
|
||||||
|
|
||||||
|
await service.updateTask(mockUserId, 'task-1', {
|
||||||
|
status: 'done',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prisma.task.update).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
data: expect.objectContaining({
|
||||||
|
completedAt: expect.any(Date),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("devrait lancer une erreur si la tâche n'existe pas", async () => {
|
||||||
|
vi.mocked(prisma.task.findFirst).mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.updateTask(mockUserId, 'non-existent', {
|
||||||
|
title: 'Updated',
|
||||||
|
})
|
||||||
|
).rejects.toThrow('introuvable');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteTask', () => {
|
||||||
|
it('devrait supprimer une tâche', async () => {
|
||||||
|
const mockTask = {
|
||||||
|
id: 'task-1',
|
||||||
|
title: 'Task to delete',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findFirst).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(prisma.task.delete).mockResolvedValue({} as any);
|
||||||
|
|
||||||
|
await service.deleteTask(mockUserId, 'task-1');
|
||||||
|
|
||||||
|
expect(prisma.task.delete).toHaveBeenCalledWith({
|
||||||
|
where: { id: 'task-1' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("devrait lancer une erreur si la tâche n'existe pas", async () => {
|
||||||
|
vi.mocked(prisma.task.findFirst).mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.deleteTask(mockUserId, 'non-existent')
|
||||||
|
).rejects.toThrow('introuvable');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateTaskStatus', () => {
|
||||||
|
it("devrait mettre à jour le statut d'une tâche", async () => {
|
||||||
|
const mockTask = {
|
||||||
|
id: 'task-1',
|
||||||
|
title: 'Task',
|
||||||
|
description: null,
|
||||||
|
status: 'todo',
|
||||||
|
priority: 'medium',
|
||||||
|
ownerId: mockUserId,
|
||||||
|
source: 'manual',
|
||||||
|
sourceId: 'manual-123',
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
dueDate: null,
|
||||||
|
completedAt: null,
|
||||||
|
primaryTagId: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
tfsProject: null,
|
||||||
|
tfsPullRequestId: null,
|
||||||
|
tfsRepository: null,
|
||||||
|
tfsSourceBranch: null,
|
||||||
|
tfsTargetBranch: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
primaryTag: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findFirst).mockResolvedValue(mockTask as any);
|
||||||
|
vi.mocked(prisma.task.update).mockResolvedValue({
|
||||||
|
...mockTask,
|
||||||
|
status: 'in_progress',
|
||||||
|
} as any);
|
||||||
|
vi.mocked(prisma.task.findUnique).mockResolvedValue({
|
||||||
|
...mockTask,
|
||||||
|
status: 'in_progress',
|
||||||
|
} as any);
|
||||||
|
vi.mocked(getReadonlyFieldsForTask).mockReturnValue([]);
|
||||||
|
|
||||||
|
const task = await service.updateTaskStatus(
|
||||||
|
mockUserId,
|
||||||
|
'task-1',
|
||||||
|
'in_progress'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(task).toBeDefined();
|
||||||
|
expect(prisma.task.update).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user