test(JiraSync): enhance test coverage for change detection logic and preserved fields
This commit is contained in:
278
src/services/analytics/__tests__/analytics.test.ts
Normal file
278
src/services/analytics/__tests__/analytics.test.ts
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
/**
|
||||||
|
* Tests unitaires pour AnalyticsService
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import { AnalyticsService } from '../analytics';
|
||||||
|
import { TaskStatus, TaskPriority, TaskSource } from '@/lib/types';
|
||||||
|
import { prisma } from '@/services/core/database';
|
||||||
|
|
||||||
|
// Mock de prisma
|
||||||
|
vi.mock('@/services/core/database', () => ({
|
||||||
|
prisma: {
|
||||||
|
task: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('AnalyticsService', () => {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const mockDate = new Date('2024-01-15T12:00:00Z');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(mockDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getProductivityMetrics', () => {
|
||||||
|
it('devrait calculer les métriques de productivité avec des tâches', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche 1',
|
||||||
|
description: 'Description 1',
|
||||||
|
status: 'done' as TaskStatus,
|
||||||
|
priority: 'high' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-12'),
|
||||||
|
completedAt: new Date('2024-01-12'),
|
||||||
|
dueDate: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche 2',
|
||||||
|
description: 'Description 2',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'jira' as TaskSource,
|
||||||
|
sourceId: 'JIRA-123',
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-13'),
|
||||||
|
updatedAt: new Date('2024-01-13'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
jiraProject: 'PROJ',
|
||||||
|
jiraKey: 'JIRA-123',
|
||||||
|
jiraType: 'Task',
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
|
||||||
|
const result = await AnalyticsService.getProductivityMetrics(userId);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('completionTrend');
|
||||||
|
expect(result).toHaveProperty('velocityData');
|
||||||
|
expect(result).toHaveProperty('priorityDistribution');
|
||||||
|
expect(result).toHaveProperty('statusFlow');
|
||||||
|
expect(result).toHaveProperty('weeklyStats');
|
||||||
|
|
||||||
|
expect(result.completionTrend.length).toBeGreaterThan(0);
|
||||||
|
expect(result.priorityDistribution.length).toBeGreaterThan(0);
|
||||||
|
expect(result.statusFlow.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait gérer une liste vide de tâches', async () => {
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = await AnalyticsService.getProductivityMetrics(userId);
|
||||||
|
|
||||||
|
expect(result.completionTrend.length).toBeGreaterThan(0);
|
||||||
|
expect(result.velocityData).toEqual([]);
|
||||||
|
expect(result.priorityDistribution).toEqual([]);
|
||||||
|
expect(result.statusFlow).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait filtrer par sources si spécifié', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche Jira',
|
||||||
|
status: 'done' as TaskStatus,
|
||||||
|
priority: 'high' as TaskPriority,
|
||||||
|
source: 'jira' as TaskSource,
|
||||||
|
sourceId: 'JIRA-1',
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-10'),
|
||||||
|
completedAt: new Date('2024-01-10'),
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche Manual',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-10'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
|
||||||
|
const result = await AnalyticsService.getProductivityMetrics(
|
||||||
|
userId,
|
||||||
|
undefined,
|
||||||
|
['jira']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Vérifier que seules les tâches Jira sont incluses dans les calculs
|
||||||
|
const allStatuses = result.statusFlow.map((s) => s.status);
|
||||||
|
expect(allStatuses).toContain('Terminé'); // Seule la tâche Jira est done
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait utiliser une période personnalisée si fournie', async () => {
|
||||||
|
const startDate = new Date('2024-01-01');
|
||||||
|
const endDate = new Date('2024-01-10');
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
|
await AnalyticsService.getProductivityMetrics(userId, {
|
||||||
|
start: startDate,
|
||||||
|
end: endDate,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prisma.task.findMany).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer correctement la tendance de completion', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche complétée',
|
||||||
|
status: 'done' as TaskStatus,
|
||||||
|
priority: 'high' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-12'),
|
||||||
|
completedAt: new Date('2024-01-12'),
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
|
||||||
|
const result = await AnalyticsService.getProductivityMetrics(userId);
|
||||||
|
|
||||||
|
const completionDay = result.completionTrend.find(
|
||||||
|
(day) => day.date === '2024-01-12'
|
||||||
|
);
|
||||||
|
expect(completionDay).toBeDefined();
|
||||||
|
expect(completionDay?.completed).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer correctement la distribution des priorités', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche haute priorité',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'high' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-10'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche moyenne priorité',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-10'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
|
||||||
|
const result = await AnalyticsService.getProductivityMetrics(userId);
|
||||||
|
|
||||||
|
const highPriority = result.priorityDistribution.find(
|
||||||
|
(p) => p.priority === 'Élevée'
|
||||||
|
);
|
||||||
|
const mediumPriority = result.priorityDistribution.find(
|
||||||
|
(p) => p.priority === 'Moyenne'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(highPriority).toBeDefined();
|
||||||
|
expect(highPriority?.count).toBe(1);
|
||||||
|
expect(highPriority?.percentage).toBe(50);
|
||||||
|
|
||||||
|
expect(mediumPriority).toBeDefined();
|
||||||
|
expect(mediumPriority?.count).toBe(1);
|
||||||
|
expect(mediumPriority?.percentage).toBe(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait gérer les erreurs de base de données', async () => {
|
||||||
|
vi.mocked(prisma.task.findMany).mockRejectedValue(
|
||||||
|
new Error('Database error')
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
AnalyticsService.getProductivityMetrics(userId)
|
||||||
|
).rejects.toThrow('Impossible de calculer les métriques de productivité');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
380
src/services/analytics/__tests__/deadline-analytics.test.ts
Normal file
380
src/services/analytics/__tests__/deadline-analytics.test.ts
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
/**
|
||||||
|
* Tests unitaires pour DeadlineAnalyticsService
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import { DeadlineAnalyticsService } from '../deadline-analytics';
|
||||||
|
import { TaskStatus } from '@/lib/types';
|
||||||
|
import { prisma } from '@/services/core/database';
|
||||||
|
|
||||||
|
// Mock de prisma
|
||||||
|
vi.mock('@/services/core/database', () => ({
|
||||||
|
prisma: {
|
||||||
|
task: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('DeadlineAnalyticsService', () => {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const mockDate = new Date('2024-01-15T12:00:00Z');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(mockDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getDeadlineMetrics', () => {
|
||||||
|
it('devrait catégoriser les tâches par urgence', async () => {
|
||||||
|
const overdueDate = new Date('2024-01-10'); // 5 jours en retard
|
||||||
|
const criticalDate = new Date('2024-01-16'); // 1 jour restant
|
||||||
|
const warningDate = new Date('2024-01-20'); // 5 jours restants
|
||||||
|
const upcomingDate = new Date('2024-01-25'); // 10 jours restants
|
||||||
|
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche en retard',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'high',
|
||||||
|
dueDate: overdueDate,
|
||||||
|
source: 'manual',
|
||||||
|
ownerId: userId,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche critique',
|
||||||
|
status: 'in_progress' as TaskStatus,
|
||||||
|
priority: 'urgent',
|
||||||
|
dueDate: criticalDate,
|
||||||
|
source: 'jira',
|
||||||
|
ownerId: userId,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
title: 'Tâche warning',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium',
|
||||||
|
dueDate: warningDate,
|
||||||
|
source: 'manual',
|
||||||
|
ownerId: userId,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
title: 'Tâche à venir',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'low',
|
||||||
|
dueDate: upcomingDate,
|
||||||
|
source: 'manual',
|
||||||
|
ownerId: userId,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
|
||||||
|
const result = await DeadlineAnalyticsService.getDeadlineMetrics(userId);
|
||||||
|
|
||||||
|
expect(result.overdue).toHaveLength(1);
|
||||||
|
expect(result.overdue[0].id).toBe('1');
|
||||||
|
expect(result.critical).toHaveLength(1);
|
||||||
|
expect(result.critical[0].id).toBe('2');
|
||||||
|
expect(result.warning).toHaveLength(1);
|
||||||
|
expect(result.warning[0].id).toBe('3');
|
||||||
|
expect(result.upcoming).toHaveLength(1);
|
||||||
|
expect(result.upcoming[0].id).toBe('4');
|
||||||
|
|
||||||
|
expect(result.summary.overdueCount).toBe(1);
|
||||||
|
expect(result.summary.criticalCount).toBe(1);
|
||||||
|
expect(result.summary.warningCount).toBe(1);
|
||||||
|
expect(result.summary.upcomingCount).toBe(1);
|
||||||
|
expect(result.summary.totalWithDeadlines).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait exclure les tâches terminées ou annulées', async () => {
|
||||||
|
// Le service filtre déjà les tâches terminées dans la requête Prisma
|
||||||
|
// donc le mock devrait retourner une liste vide
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = await DeadlineAnalyticsService.getDeadlineMetrics(userId);
|
||||||
|
|
||||||
|
expect(result.summary.totalWithDeadlines).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait filtrer par sources si spécifié', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche Jira',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'high',
|
||||||
|
dueDate: new Date('2024-01-16'),
|
||||||
|
source: 'jira',
|
||||||
|
ownerId: userId,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche Manual',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium',
|
||||||
|
dueDate: new Date('2024-01-17'),
|
||||||
|
source: 'manual',
|
||||||
|
ownerId: userId,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
|
||||||
|
const result = await DeadlineAnalyticsService.getDeadlineMetrics(userId, [
|
||||||
|
'jira',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const allTaskIds = [
|
||||||
|
...result.overdue,
|
||||||
|
...result.critical,
|
||||||
|
...result.warning,
|
||||||
|
...result.upcoming,
|
||||||
|
].map((t) => t.id);
|
||||||
|
|
||||||
|
expect(allTaskIds).toContain('1');
|
||||||
|
expect(allTaskIds).not.toContain('2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer correctement les jours restants', async () => {
|
||||||
|
// Utiliser une date normalisée pour éviter les problèmes de timezone
|
||||||
|
// La date mockée est le 15 janvier à 12:00, donc le 20 janvier à 00:00
|
||||||
|
// donne environ 4-5 jours selon le calcul avec Math.ceil
|
||||||
|
const dueDate = new Date('2024-01-20T00:00:00Z'); // 5 jours dans le futur
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche test',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium',
|
||||||
|
dueDate,
|
||||||
|
source: 'manual',
|
||||||
|
ownerId: userId,
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
|
||||||
|
const result = await DeadlineAnalyticsService.getDeadlineMetrics(userId);
|
||||||
|
|
||||||
|
// Le calcul utilise Math.ceil, donc avec les heures ça peut donner 5 ou 6 jours
|
||||||
|
// Vérifions que c'est dans la plage warning (3-7 jours)
|
||||||
|
expect(result.warning[0].daysRemaining).toBeGreaterThanOrEqual(5);
|
||||||
|
expect(result.warning[0].daysRemaining).toBeLessThanOrEqual(6);
|
||||||
|
expect(result.warning[0].urgencyLevel).toBe('warning');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait gérer les erreurs de base de données', async () => {
|
||||||
|
vi.mocked(prisma.task.findMany).mockRejectedValue(
|
||||||
|
new Error('Database error')
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
DeadlineAnalyticsService.getDeadlineMetrics(userId)
|
||||||
|
).rejects.toThrow("Impossible d'analyser les échéances");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCriticalDeadlines', () => {
|
||||||
|
it('devrait retourner les 10 tâches les plus critiques', async () => {
|
||||||
|
const mockTasks = Array.from({ length: 15 }, (_, i) => ({
|
||||||
|
id: `${i + 1}`,
|
||||||
|
title: `Tâche ${i + 1}`,
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'high',
|
||||||
|
dueDate: new Date(`2024-01-${10 + i}`), // Dates en retard
|
||||||
|
source: 'manual',
|
||||||
|
ownerId: userId,
|
||||||
|
taskTags: [],
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
|
||||||
|
const result =
|
||||||
|
await DeadlineAnalyticsService.getCriticalDeadlines(userId);
|
||||||
|
|
||||||
|
expect(result.length).toBeLessThanOrEqual(10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('analyzeImpactByPriority', () => {
|
||||||
|
it("devrait analyser l'impact par priorité", () => {
|
||||||
|
const tasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche haute priorité en retard',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'high',
|
||||||
|
dueDate: new Date('2024-01-10'),
|
||||||
|
daysRemaining: -5,
|
||||||
|
urgencyLevel: 'overdue' as const,
|
||||||
|
source: 'manual',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche haute priorité critique',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'high',
|
||||||
|
dueDate: new Date('2024-01-16'),
|
||||||
|
daysRemaining: 1,
|
||||||
|
urgencyLevel: 'critical' as const,
|
||||||
|
source: 'manual',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
title: 'Tâche moyenne priorité',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium',
|
||||||
|
dueDate: new Date('2024-01-20'),
|
||||||
|
daysRemaining: 5,
|
||||||
|
urgencyLevel: 'warning' as const,
|
||||||
|
source: 'manual',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = DeadlineAnalyticsService.analyzeImpactByPriority(tasks);
|
||||||
|
|
||||||
|
expect(result.length).toBeGreaterThan(0);
|
||||||
|
const highPriority = result.find((p) => p.priority === 'high');
|
||||||
|
expect(highPriority).toBeDefined();
|
||||||
|
expect(highPriority?.overdueCount).toBe(1);
|
||||||
|
expect(highPriority?.criticalCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait trier par impact décroissant', () => {
|
||||||
|
const tasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche haute priorité',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'high',
|
||||||
|
dueDate: new Date('2024-01-10'),
|
||||||
|
daysRemaining: -5,
|
||||||
|
urgencyLevel: 'overdue' as const,
|
||||||
|
source: 'manual',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche basse priorité',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'low',
|
||||||
|
dueDate: new Date('2024-01-20'),
|
||||||
|
daysRemaining: 5,
|
||||||
|
urgencyLevel: 'warning' as const,
|
||||||
|
source: 'manual',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = DeadlineAnalyticsService.analyzeImpactByPriority(tasks);
|
||||||
|
|
||||||
|
expect(result[0].priority).toBe('high'); // Plus d'impact
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('calculateRiskMetrics', () => {
|
||||||
|
it('devrait calculer un score de risque faible', () => {
|
||||||
|
const metrics = {
|
||||||
|
overdue: [],
|
||||||
|
critical: [],
|
||||||
|
warning: [],
|
||||||
|
upcoming: [],
|
||||||
|
summary: {
|
||||||
|
overdueCount: 0,
|
||||||
|
criticalCount: 0,
|
||||||
|
warningCount: 1,
|
||||||
|
upcomingCount: 2,
|
||||||
|
totalWithDeadlines: 3,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = DeadlineAnalyticsService.calculateRiskMetrics(metrics);
|
||||||
|
|
||||||
|
expect(result.riskScore).toBeLessThan(25);
|
||||||
|
expect(result.riskLevel).toBe('low');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer un score de risque critique', () => {
|
||||||
|
const metrics = {
|
||||||
|
overdue: [],
|
||||||
|
critical: [],
|
||||||
|
warning: [],
|
||||||
|
upcoming: [],
|
||||||
|
summary: {
|
||||||
|
overdueCount: 3,
|
||||||
|
criticalCount: 2,
|
||||||
|
warningCount: 0,
|
||||||
|
upcomingCount: 0,
|
||||||
|
totalWithDeadlines: 5,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = DeadlineAnalyticsService.calculateRiskMetrics(metrics);
|
||||||
|
|
||||||
|
expect(result.riskScore).toBeGreaterThanOrEqual(75);
|
||||||
|
expect(result.riskLevel).toBe('critical');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait limiter le score à 100', () => {
|
||||||
|
const metrics = {
|
||||||
|
overdue: [],
|
||||||
|
critical: [],
|
||||||
|
warning: [],
|
||||||
|
upcoming: [],
|
||||||
|
summary: {
|
||||||
|
overdueCount: 10,
|
||||||
|
criticalCount: 10,
|
||||||
|
warningCount: 10,
|
||||||
|
upcomingCount: 10,
|
||||||
|
totalWithDeadlines: 40,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = DeadlineAnalyticsService.calculateRiskMetrics(metrics);
|
||||||
|
|
||||||
|
expect(result.riskScore).toBeLessThanOrEqual(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait fournir des recommandations appropriées', () => {
|
||||||
|
const metrics = {
|
||||||
|
overdue: [],
|
||||||
|
critical: [],
|
||||||
|
warning: [],
|
||||||
|
upcoming: [],
|
||||||
|
summary: {
|
||||||
|
overdueCount: 2,
|
||||||
|
criticalCount: 1,
|
||||||
|
warningCount: 2,
|
||||||
|
upcomingCount: 1,
|
||||||
|
totalWithDeadlines: 6,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = DeadlineAnalyticsService.calculateRiskMetrics(metrics);
|
||||||
|
|
||||||
|
expect(result.recommendation).toBeDefined();
|
||||||
|
expect(result.recommendation.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
314
src/services/analytics/__tests__/manager-summary.test.ts
Normal file
314
src/services/analytics/__tests__/manager-summary.test.ts
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
/**
|
||||||
|
* Tests unitaires pour ManagerSummaryService
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import { ManagerSummaryService } from '../manager-summary';
|
||||||
|
import { prisma } from '@/services/core/database';
|
||||||
|
|
||||||
|
// Mock de prisma
|
||||||
|
vi.mock('@/services/core/database', () => ({
|
||||||
|
prisma: {
|
||||||
|
task: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
},
|
||||||
|
dailyCheckbox: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
count: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('ManagerSummaryService', () => {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const mockDate = new Date('2024-01-15T12:00:00Z');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(mockDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getManagerSummary', () => {
|
||||||
|
it('devrait générer un résumé pour les 7 derniers jours', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche complétée',
|
||||||
|
description: 'Description',
|
||||||
|
priority: 'high',
|
||||||
|
completedAt: new Date('2024-01-12'),
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-12'),
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockCheckboxes = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: 'Todo complété',
|
||||||
|
isChecked: true,
|
||||||
|
type: 'task',
|
||||||
|
date: new Date('2024-01-13'),
|
||||||
|
createdAt: new Date('2024-01-13'),
|
||||||
|
task: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany).mockResolvedValue(
|
||||||
|
mockCheckboxes as any
|
||||||
|
);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0);
|
||||||
|
|
||||||
|
const result = await ManagerSummaryService.getManagerSummary(userId);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('period');
|
||||||
|
expect(result).toHaveProperty('keyAccomplishments');
|
||||||
|
expect(result).toHaveProperty('upcomingChallenges');
|
||||||
|
expect(result).toHaveProperty('metrics');
|
||||||
|
expect(result).toHaveProperty('narrative');
|
||||||
|
|
||||||
|
expect(result.period.start).toBeInstanceOf(Date);
|
||||||
|
expect(result.period.end).toBeInstanceOf(Date);
|
||||||
|
expect(result.keyAccomplishments.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait extraire les accomplissements clés des tâches', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche haute priorité',
|
||||||
|
description: 'Description importante',
|
||||||
|
priority: 'high',
|
||||||
|
completedAt: new Date('2024-01-12'),
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-12'),
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany).mockResolvedValue([]);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0);
|
||||||
|
|
||||||
|
const result = await ManagerSummaryService.getManagerSummary(userId);
|
||||||
|
|
||||||
|
const accomplishment = result.keyAccomplishments.find(
|
||||||
|
(a) => a.id === 'task-1'
|
||||||
|
);
|
||||||
|
expect(accomplishment).toBeDefined();
|
||||||
|
expect(accomplishment?.impact).toBe('high');
|
||||||
|
expect(accomplishment?.tags).toContain('important');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait inclure les todos standalone comme accomplissements', async () => {
|
||||||
|
const mockCheckboxes = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: 'Réunion importante',
|
||||||
|
isChecked: true,
|
||||||
|
type: 'meeting',
|
||||||
|
date: new Date('2024-01-13'),
|
||||||
|
createdAt: new Date('2024-01-13'),
|
||||||
|
task: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue([]);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany).mockResolvedValue(
|
||||||
|
mockCheckboxes as any
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await ManagerSummaryService.getManagerSummary(userId);
|
||||||
|
|
||||||
|
const accomplishment = result.keyAccomplishments.find(
|
||||||
|
(a) => a.id === 'todo-1'
|
||||||
|
);
|
||||||
|
expect(accomplishment).toBeDefined();
|
||||||
|
expect(accomplishment?.title).toContain('Réunion importante');
|
||||||
|
expect(accomplishment?.impact).toBe('low');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait identifier les défis à venir', async () => {
|
||||||
|
const mockUpcomingTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche à venir',
|
||||||
|
description: 'Description',
|
||||||
|
priority: 'high',
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockUpcomingCheckboxes = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: 'Todo à venir',
|
||||||
|
isChecked: false,
|
||||||
|
type: 'task',
|
||||||
|
date: new Date('2024-01-20'),
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
task: {
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche à venir',
|
||||||
|
priority: 'high',
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mock pour getCompletedTasks
|
||||||
|
vi.mocked(prisma.task.findMany)
|
||||||
|
.mockResolvedValueOnce([]) // Tâches complétées
|
||||||
|
.mockResolvedValueOnce(mockUpcomingTasks as any); // Tâches à venir
|
||||||
|
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany)
|
||||||
|
.mockResolvedValueOnce([]) // Checkboxes complétées
|
||||||
|
.mockResolvedValueOnce(mockUpcomingCheckboxes as any); // Checkboxes à venir
|
||||||
|
|
||||||
|
vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0);
|
||||||
|
|
||||||
|
const result = await ManagerSummaryService.getManagerSummary(userId);
|
||||||
|
|
||||||
|
expect(result.upcomingChallenges.length).toBeGreaterThan(0);
|
||||||
|
const challenge = result.upcomingChallenges.find(
|
||||||
|
(c) => c.id === 'task-1'
|
||||||
|
);
|
||||||
|
expect(challenge).toBeDefined();
|
||||||
|
expect(challenge?.priority).toBe('high');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer les métriques correctement', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche haute priorité',
|
||||||
|
description: 'Description',
|
||||||
|
priority: 'high',
|
||||||
|
completedAt: new Date('2024-01-12'),
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-12'),
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockCheckboxes = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
text: 'Réunion',
|
||||||
|
isChecked: true,
|
||||||
|
type: 'meeting',
|
||||||
|
date: new Date('2024-01-13'),
|
||||||
|
createdAt: new Date('2024-01-13'),
|
||||||
|
task: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany).mockResolvedValue(
|
||||||
|
mockCheckboxes as any
|
||||||
|
);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0);
|
||||||
|
|
||||||
|
const result = await ManagerSummaryService.getManagerSummary(userId);
|
||||||
|
|
||||||
|
expect(result.metrics.totalTasksCompleted).toBe(1);
|
||||||
|
expect(result.metrics.totalCheckboxesCompleted).toBe(1);
|
||||||
|
expect(result.metrics.highPriorityTasksCompleted).toBe(1);
|
||||||
|
expect(result.metrics.meetingCheckboxesCompleted).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait générer un narratif', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche importante',
|
||||||
|
description: 'Description',
|
||||||
|
priority: 'high',
|
||||||
|
completedAt: new Date('2024-01-12'),
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-12'),
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany).mockResolvedValue([]);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0);
|
||||||
|
|
||||||
|
const result = await ManagerSummaryService.getManagerSummary(userId);
|
||||||
|
|
||||||
|
expect(result.narrative).toHaveProperty('weekHighlight');
|
||||||
|
expect(result.narrative).toHaveProperty('mainChallenges');
|
||||||
|
expect(result.narrative).toHaveProperty('nextWeekFocus');
|
||||||
|
expect(result.narrative.weekHighlight.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait utiliser une date personnalisée si fournie', async () => {
|
||||||
|
const customDate = new Date('2024-02-01');
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue([]);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = await ManagerSummaryService.getManagerSummary(
|
||||||
|
userId,
|
||||||
|
customDate
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.period.end).toEqual(customDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait trier les accomplissements par impact', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche haute priorité',
|
||||||
|
description: 'Description',
|
||||||
|
priority: 'high',
|
||||||
|
completedAt: new Date('2024-01-12'),
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-12'),
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche basse priorité',
|
||||||
|
description: 'Description',
|
||||||
|
priority: 'low',
|
||||||
|
completedAt: new Date('2024-01-13'),
|
||||||
|
createdAt: new Date('2024-01-11'),
|
||||||
|
updatedAt: new Date('2024-01-13'),
|
||||||
|
taskTags: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.findMany).mockResolvedValue([]);
|
||||||
|
vi.mocked(prisma.dailyCheckbox.count).mockResolvedValue(0);
|
||||||
|
|
||||||
|
const result = await ManagerSummaryService.getManagerSummary(userId);
|
||||||
|
|
||||||
|
expect(result.keyAccomplishments[0].impact).toBe('high');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
234
src/services/analytics/__tests__/metrics.test.ts
Normal file
234
src/services/analytics/__tests__/metrics.test.ts
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
/**
|
||||||
|
* Tests unitaires pour MetricsService
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import { MetricsService } from '../metrics';
|
||||||
|
import { prisma } from '@/services/core/database';
|
||||||
|
|
||||||
|
// Mock de prisma
|
||||||
|
vi.mock('@/services/core/database', () => ({
|
||||||
|
prisma: {
|
||||||
|
task: {
|
||||||
|
count: vi.fn(),
|
||||||
|
groupBy: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('MetricsService', () => {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const mockDate = new Date('2024-01-15T12:00:00Z');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(mockDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getWeeklyMetrics', () => {
|
||||||
|
it('devrait retourner les métriques hebdomadaires', async () => {
|
||||||
|
// Mock des comptages pour chaque jour
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(5) // completed
|
||||||
|
.mockResolvedValueOnce(3) // inProgress
|
||||||
|
.mockResolvedValueOnce(1) // blocked
|
||||||
|
.mockResolvedValueOnce(2) // pending
|
||||||
|
.mockResolvedValueOnce(2) // newTasks
|
||||||
|
.mockResolvedValueOnce(10) // totalTasks
|
||||||
|
.mockResolvedValueOnce(5) // completed jour 2
|
||||||
|
.mockResolvedValueOnce(3) // inProgress jour 2
|
||||||
|
.mockResolvedValueOnce(1) // blocked jour 2
|
||||||
|
.mockResolvedValueOnce(2) // pending jour 2
|
||||||
|
.mockResolvedValueOnce(2) // newTasks jour 2
|
||||||
|
.mockResolvedValueOnce(10); // totalTasks jour 2
|
||||||
|
|
||||||
|
// Mock pour les autres jours (répéter pour 7 jours)
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock pour statusDistribution
|
||||||
|
vi.mocked(prisma.task.groupBy).mockResolvedValueOnce([
|
||||||
|
{
|
||||||
|
status: 'done',
|
||||||
|
_count: { status: 5 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 'in_progress',
|
||||||
|
_count: { status: 3 },
|
||||||
|
},
|
||||||
|
] as any);
|
||||||
|
|
||||||
|
// Mock pour priorityBreakdown (3 priorités × 1 count each)
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(2) // high completed
|
||||||
|
.mockResolvedValueOnce(1) // high inProgress
|
||||||
|
.mockResolvedValueOnce(2) // medium completed
|
||||||
|
.mockResolvedValueOnce(1) // medium inProgress
|
||||||
|
.mockResolvedValueOnce(1) // low completed
|
||||||
|
.mockResolvedValueOnce(1); // low inProgress
|
||||||
|
|
||||||
|
const result = await MetricsService.getWeeklyMetrics(userId);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('period');
|
||||||
|
expect(result).toHaveProperty('dailyBreakdown');
|
||||||
|
expect(result).toHaveProperty('summary');
|
||||||
|
expect(result).toHaveProperty('statusDistribution');
|
||||||
|
expect(result).toHaveProperty('priorityBreakdown');
|
||||||
|
|
||||||
|
expect(result.dailyBreakdown).toHaveLength(7);
|
||||||
|
expect(result.summary).toHaveProperty('totalTasksCompleted');
|
||||||
|
expect(result.summary).toHaveProperty('totalTasksCreated');
|
||||||
|
expect(result.summary).toHaveProperty('averageCompletionRate');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer correctement le taux de completion', async () => {
|
||||||
|
// Mock pour un jour avec des tâches complétées
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(5) // completed
|
||||||
|
.mockResolvedValueOnce(0) // inProgress
|
||||||
|
.mockResolvedValueOnce(0) // blocked
|
||||||
|
.mockResolvedValueOnce(0) // pending
|
||||||
|
.mockResolvedValueOnce(0) // newTasks
|
||||||
|
.mockResolvedValueOnce(10); // totalTasks
|
||||||
|
|
||||||
|
// Mock pour les autres jours
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.groupBy).mockResolvedValueOnce([] as any);
|
||||||
|
|
||||||
|
// Mock priorityBreakdown
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0);
|
||||||
|
|
||||||
|
const result = await MetricsService.getWeeklyMetrics(userId);
|
||||||
|
|
||||||
|
expect(result.dailyBreakdown[0].completionRate).toBe(50);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait identifier le jour de pic de productivité', async () => {
|
||||||
|
// Jour 1: 10 complétées
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(10)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(10);
|
||||||
|
|
||||||
|
// Jour 2-7: 0 complétées
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.groupBy).mockResolvedValueOnce([] as any);
|
||||||
|
|
||||||
|
// Mock priorityBreakdown
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0)
|
||||||
|
.mockResolvedValueOnce(0);
|
||||||
|
|
||||||
|
const result = await MetricsService.getWeeklyMetrics(userId);
|
||||||
|
|
||||||
|
expect(result.summary.totalTasksCompleted).toBe(10);
|
||||||
|
expect(result.summary.peakProductivityDay).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait utiliser une date personnalisée si fournie', async () => {
|
||||||
|
const customDate = new Date('2024-02-01');
|
||||||
|
|
||||||
|
// Mock minimal pour éviter les erreurs
|
||||||
|
vi.mocked(prisma.task.count).mockResolvedValue(0);
|
||||||
|
vi.mocked(prisma.task.groupBy).mockResolvedValue([]);
|
||||||
|
|
||||||
|
await MetricsService.getWeeklyMetrics(userId, customDate);
|
||||||
|
|
||||||
|
expect(prisma.task.count).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getVelocityTrends', () => {
|
||||||
|
it('devrait retourner les tendances de vélocité', async () => {
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(5) // completed semaine 1
|
||||||
|
.mockResolvedValueOnce(3) // created semaine 1
|
||||||
|
.mockResolvedValueOnce(7) // completed semaine 2
|
||||||
|
.mockResolvedValueOnce(4) // created semaine 2
|
||||||
|
.mockResolvedValueOnce(6) // completed semaine 3
|
||||||
|
.mockResolvedValueOnce(5) // created semaine 3
|
||||||
|
.mockResolvedValueOnce(8) // completed semaine 4
|
||||||
|
.mockResolvedValueOnce(6); // created semaine 4
|
||||||
|
|
||||||
|
const result = await MetricsService.getVelocityTrends(userId, 4);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(4);
|
||||||
|
expect(result[0]).toHaveProperty('date');
|
||||||
|
expect(result[0]).toHaveProperty('completed');
|
||||||
|
expect(result[0]).toHaveProperty('created');
|
||||||
|
expect(result[0]).toHaveProperty('velocity');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer correctement la vélocité', async () => {
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(10) // completed
|
||||||
|
.mockResolvedValueOnce(5); // created
|
||||||
|
|
||||||
|
const result = await MetricsService.getVelocityTrends(userId, 1);
|
||||||
|
|
||||||
|
expect(result[0].velocity).toBe(200); // 10/5 * 100
|
||||||
|
});
|
||||||
|
|
||||||
|
it("devrait gérer le cas où aucune tâche n'est créée", async () => {
|
||||||
|
vi.mocked(prisma.task.count)
|
||||||
|
.mockResolvedValueOnce(5) // completed
|
||||||
|
.mockResolvedValueOnce(0); // created
|
||||||
|
|
||||||
|
const result = await MetricsService.getVelocityTrends(userId, 1);
|
||||||
|
|
||||||
|
expect(result[0].velocity).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait utiliser le nombre de semaines personnalisé', async () => {
|
||||||
|
vi.mocked(prisma.task.count).mockResolvedValue(0);
|
||||||
|
|
||||||
|
const result = await MetricsService.getVelocityTrends(userId, 8);
|
||||||
|
|
||||||
|
expect(result).toHaveLength(8);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
502
src/services/analytics/__tests__/tag-analytics.test.ts
Normal file
502
src/services/analytics/__tests__/tag-analytics.test.ts
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
/**
|
||||||
|
* Tests unitaires pour TagAnalyticsService
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import { TagAnalyticsService } from '../tag-analytics';
|
||||||
|
import { TaskStatus, TaskPriority, TaskSource } from '@/lib/types';
|
||||||
|
import { prisma } from '@/services/core/database';
|
||||||
|
|
||||||
|
// Mock de prisma
|
||||||
|
vi.mock('@/services/core/database', () => ({
|
||||||
|
prisma: {
|
||||||
|
task: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
},
|
||||||
|
tag: {
|
||||||
|
findMany: vi.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('TagAnalyticsService', () => {
|
||||||
|
const userId = 'test-user-id';
|
||||||
|
const mockDate = new Date('2024-01-15T12:00:00Z');
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
vi.useFakeTimers();
|
||||||
|
vi.setSystemTime(mockDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getTagDistributionMetrics', () => {
|
||||||
|
it('devrait calculer la distribution des tags', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche avec tag',
|
||||||
|
description: 'Description',
|
||||||
|
status: 'done' as TaskStatus,
|
||||||
|
priority: 'high' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-12'),
|
||||||
|
completedAt: new Date('2024-01-12'),
|
||||||
|
dueDate: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
color: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Autre tâche',
|
||||||
|
description: 'Description',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-11'),
|
||||||
|
updatedAt: new Date('2024-01-11'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
color: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'urgent',
|
||||||
|
color: '#00ff00',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockTags = [
|
||||||
|
{ name: 'important', color: '#ff0000' },
|
||||||
|
{ name: 'urgent', color: '#00ff00' },
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.tag.findMany).mockResolvedValue(mockTags as any);
|
||||||
|
|
||||||
|
const result =
|
||||||
|
await TagAnalyticsService.getTagDistributionMetrics(userId);
|
||||||
|
|
||||||
|
expect(result).toHaveProperty('tagDistribution');
|
||||||
|
expect(result).toHaveProperty('topTags');
|
||||||
|
expect(result).toHaveProperty('tagTrends');
|
||||||
|
expect(result).toHaveProperty('tagStats');
|
||||||
|
|
||||||
|
expect(result.tagDistribution.length).toBeGreaterThan(0);
|
||||||
|
const importantTag = result.tagDistribution.find(
|
||||||
|
(t) => t.tagName === 'important'
|
||||||
|
);
|
||||||
|
expect(importantTag).toBeDefined();
|
||||||
|
expect(importantTag?.count).toBe(2);
|
||||||
|
expect(importantTag?.percentage).toBe(100); // 2 tâches sur 2
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer les top tags avec métriques', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche complétée',
|
||||||
|
status: 'done' as TaskStatus,
|
||||||
|
priority: 'high' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-12'),
|
||||||
|
completedAt: new Date('2024-01-12'),
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
color: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche non complétée',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-11'),
|
||||||
|
updatedAt: new Date('2024-01-11'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
color: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockTags = [{ name: 'important', color: '#ff0000' }];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.tag.findMany).mockResolvedValue(mockTags as any);
|
||||||
|
|
||||||
|
const result =
|
||||||
|
await TagAnalyticsService.getTagDistributionMetrics(userId);
|
||||||
|
|
||||||
|
const importantTag = result.topTags.find(
|
||||||
|
(t) => t.tagName === 'important'
|
||||||
|
);
|
||||||
|
expect(importantTag).toBeDefined();
|
||||||
|
expect(importantTag?.usage).toBe(2);
|
||||||
|
expect(importantTag?.completionRate).toBe(50); // 1 complétée sur 2
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer les tendances des tags', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-10'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
color: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockTags = [{ name: 'important', color: '#ff0000' }];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.tag.findMany).mockResolvedValue(mockTags as any);
|
||||||
|
|
||||||
|
const result =
|
||||||
|
await TagAnalyticsService.getTagDistributionMetrics(userId);
|
||||||
|
|
||||||
|
expect(result.tagTrends.length).toBeGreaterThan(0);
|
||||||
|
const trend = result.tagTrends.find((t) => t.tagName === 'important');
|
||||||
|
expect(trend).toBeDefined();
|
||||||
|
expect(trend?.count).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait calculer les statistiques des tags', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche 1',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-10'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
color: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche 2',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-11'),
|
||||||
|
updatedAt: new Date('2024-01-11'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
color: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'urgent',
|
||||||
|
color: '#00ff00',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockTags = [
|
||||||
|
{ name: 'important', color: '#ff0000' },
|
||||||
|
{ name: 'urgent', color: '#00ff00' },
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.tag.findMany).mockResolvedValue(mockTags as any);
|
||||||
|
|
||||||
|
const result =
|
||||||
|
await TagAnalyticsService.getTagDistributionMetrics(userId);
|
||||||
|
|
||||||
|
expect(result.tagStats.totalTags).toBe(2);
|
||||||
|
expect(result.tagStats.activeTags).toBe(2);
|
||||||
|
expect(result.tagStats.mostUsedTag).toBe('important');
|
||||||
|
expect(result.tagStats.avgTasksPerTag).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait filtrer par sources si spécifié', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche Jira',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'jira' as TaskSource,
|
||||||
|
sourceId: 'JIRA-1',
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-10'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: 'PROJ',
|
||||||
|
jiraKey: 'JIRA-1',
|
||||||
|
jiraType: 'Task',
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'important',
|
||||||
|
color: '#ff0000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche Manual',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-11'),
|
||||||
|
updatedAt: new Date('2024-01-11'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'urgent',
|
||||||
|
color: '#00ff00',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockTags = [
|
||||||
|
{ name: 'important', color: '#ff0000' },
|
||||||
|
{ name: 'urgent', color: '#00ff00' },
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.tag.findMany).mockResolvedValue(mockTags as any);
|
||||||
|
|
||||||
|
const result = await TagAnalyticsService.getTagDistributionMetrics(
|
||||||
|
userId,
|
||||||
|
undefined,
|
||||||
|
['jira']
|
||||||
|
);
|
||||||
|
|
||||||
|
const tagNames = result.tagDistribution.map((t) => t.tagName);
|
||||||
|
expect(tagNames).toContain('important');
|
||||||
|
expect(tagNames).not.toContain('urgent');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait utiliser une période personnalisée si fournie', async () => {
|
||||||
|
const startDate = new Date('2024-01-01');
|
||||||
|
const endDate = new Date('2024-01-10');
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue([]);
|
||||||
|
vi.mocked(prisma.tag.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
|
await TagAnalyticsService.getTagDistributionMetrics(userId, {
|
||||||
|
start: startDate,
|
||||||
|
end: endDate,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prisma.task.findMany).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait gérer les erreurs de base de données', async () => {
|
||||||
|
vi.mocked(prisma.task.findMany).mockRejectedValue(
|
||||||
|
new Error('Database error')
|
||||||
|
);
|
||||||
|
vi.mocked(prisma.tag.findMany).mockResolvedValue([]);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
TagAnalyticsService.getTagDistributionMetrics(userId)
|
||||||
|
).rejects.toThrow(
|
||||||
|
'Impossible de calculer les métriques de distribution par tags'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('devrait trier les tags par utilisation décroissante', async () => {
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Tâche',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-10'),
|
||||||
|
updatedAt: new Date('2024-01-10'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'frequent',
|
||||||
|
color: '#0000ff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Tâche',
|
||||||
|
status: 'todo' as TaskStatus,
|
||||||
|
priority: 'medium' as TaskPriority,
|
||||||
|
source: 'manual' as TaskSource,
|
||||||
|
sourceId: null,
|
||||||
|
ownerId: userId,
|
||||||
|
createdAt: new Date('2024-01-11'),
|
||||||
|
updatedAt: new Date('2024-01-11'),
|
||||||
|
completedAt: null,
|
||||||
|
dueDate: null,
|
||||||
|
description: null,
|
||||||
|
jiraProject: null,
|
||||||
|
jiraKey: null,
|
||||||
|
jiraType: null,
|
||||||
|
assignee: null,
|
||||||
|
taskTags: [
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'frequent',
|
||||||
|
color: '#0000ff',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tag: {
|
||||||
|
name: 'rare',
|
||||||
|
color: '#ffff00',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockTags = [
|
||||||
|
{ name: 'frequent', color: '#0000ff' },
|
||||||
|
{ name: 'rare', color: '#ffff00' },
|
||||||
|
];
|
||||||
|
|
||||||
|
vi.mocked(prisma.task.findMany).mockResolvedValue(mockTasks as any);
|
||||||
|
vi.mocked(prisma.tag.findMany).mockResolvedValue(mockTags as any);
|
||||||
|
|
||||||
|
const result =
|
||||||
|
await TagAnalyticsService.getTagDistributionMetrics(userId);
|
||||||
|
|
||||||
|
expect(result.tagDistribution[0].tagName).toBe('frequent');
|
||||||
|
expect(result.tagDistribution[0].count).toBeGreaterThan(
|
||||||
|
result.tagDistribution[1]?.count || 0
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user