using App.Core.Entities.Maintenance; using App.Core.Services.Kpi; namespace App.Core.Tests.Kpi; public class KpiCalculatorTests { [Fact] public void ComputeMtbfHours_LessThanTwoFailures_ReturnsNull() { var timestamps = new List { new(2026, 1, 1) }; Assert.Null(KpiCalculator.ComputeMtbfHours(timestamps)); Assert.Null(KpiCalculator.ComputeMtbfHours(new List())); } [Fact] public void ComputeMtbfHours_AveragesIntervalsBetweenFailures() { // Pannes le 1er, le 3 (48h après) et le 8 (120h après) -> moyenne (48+120)/2 = 84h. var timestamps = new List { new(2026, 1, 1), new(2026, 1, 3), new(2026, 1, 8), }; var mtbf = KpiCalculator.ComputeMtbfHours(timestamps); Assert.Equal(84, mtbf); } [Fact] public void ComputeMttrHours_NoCompletedCorrective_ReturnsNull() { var workOrders = new List { new() { Type = WorkOrderType.Corrective, Status = WorkOrderStatus.InProgress, CreatedAtUtc = DateTime.UtcNow, MachineId = 1, ScheduledDate = DateOnly.FromDateTime(DateTime.UtcNow) }, }; Assert.Null(KpiCalculator.ComputeMttrHours(workOrders)); } [Fact] public void ComputeMttrHours_AveragesRepairDuration() { var created = new DateTime(2026, 1, 1, 8, 0, 0, DateTimeKind.Utc); var workOrders = new List { new() { Type = WorkOrderType.Corrective, Status = WorkOrderStatus.Done, MachineId = 1, ScheduledDate = DateOnly.FromDateTime(created), CreatedAtUtc = created, CompletedAtUtc = created.AddHours(4), }, new() { Type = WorkOrderType.Corrective, Status = WorkOrderStatus.Done, MachineId = 1, ScheduledDate = DateOnly.FromDateTime(created), CreatedAtUtc = created, CompletedAtUtc = created.AddHours(8), }, }; Assert.Equal(6, KpiCalculator.ComputeMttrHours(workOrders)); } [Fact] public void ComputeAvailabilityPercent_NullInputs_ReturnsNull() { Assert.Null(KpiCalculator.ComputeAvailabilityPercent(null, 5)); Assert.Null(KpiCalculator.ComputeAvailabilityPercent(5, null)); } [Fact] public void ComputeAvailabilityPercent_ComputesClassicFormula() { // MTBF=90, MTTR=10 -> 90/(90+10) = 90% var availability = KpiCalculator.ComputeAvailabilityPercent(90, 10); Assert.Equal(90.0, availability); } [Fact] public void ComputeMaintenanceCost_SumsPartsAndLabor() { var parts = new List<(decimal UnitPrice, double Quantity)> { (10.00m, 3), // 30 (5.50m, 2), // 11 }; var cost = KpiCalculator.ComputeMaintenanceCost(parts, laborCost: 100m); Assert.Equal(141.00m, cost); } [Fact] public void ComputePreventiveComplianceRatePercent_NoClosedOrders_ReturnsNull() { var workOrders = new List { new() { Type = WorkOrderType.Preventive, Status = WorkOrderStatus.ToDo, MachineId = 1, ScheduledDate = new DateOnly(2026, 1, 1), CreatedAtUtc = DateTime.UtcNow }, }; Assert.Null(KpiCalculator.ComputePreventiveComplianceRatePercent(workOrders)); } [Fact] public void ComputePreventiveComplianceRatePercent_CountsOnTimeVsLate() { var workOrders = new List { // Clôturé avant échéance -> dans les temps new() { Type = WorkOrderType.Preventive, Status = WorkOrderStatus.Done, MachineId = 1, ScheduledDate = new DateOnly(2026, 1, 10), CreatedAtUtc = DateTime.UtcNow, CompletedAtUtc = new DateTime(2026, 1, 9), }, // Clôturé après échéance -> en retard new() { Type = WorkOrderType.Preventive, Status = WorkOrderStatus.Done, MachineId = 1, ScheduledDate = new DateOnly(2026, 1, 10), CreatedAtUtc = DateTime.UtcNow, CompletedAtUtc = new DateTime(2026, 1, 15), }, // Curatif : ignoré du calcul new() { Type = WorkOrderType.Corrective, Status = WorkOrderStatus.Done, MachineId = 1, ScheduledDate = new DateOnly(2026, 1, 10), CreatedAtUtc = DateTime.UtcNow, CompletedAtUtc = new DateTime(2026, 1, 20), }, }; var rate = KpiCalculator.ComputePreventiveComplianceRatePercent(workOrders); Assert.Equal(50.0, rate); } }