Precision configurable des distances (arrondi superieur 0/1/2 decimales)
- Nouveau parametre global Precision_Distance (sous-menu Application de la Page_Parametrage, entre Compte et Vehicules), persiste via une entite singleton ParametresApplication, expose par GET/PUT /api/parametres-application - Arrondi superieur (ceiling) applique a l'affichage et aux agregats : trajet simple, aller/retour (arrondi puis doublement), totaux jour/mois/annee, carte, export PDF et bareme kilometrique - jamais a la distance brute stockee - Refonte du Montant_Previsionnel : le montant mensuel (80%) porte desormais sur le seul mois affiche (tranche determinee par le cumul annuel) ; nouvelle ligne Montant_Previsionnel_Annuel (100%, sans minoration, sur le cumul) - Backend : PrecisionDistanceService (ArrondirDistance), integration dans ResumeService/TotauxService/RemboursementService/MontantRegleService, migration EF Core ; 7 tests de propriete (FsCheck) + tests existants, 62/62 verts - Frontend : ParametresApplicationService (signal partage), composant de parametrage, fonction miroir arrondirDistance, adaptation de la liste des trajets, du panneau des totaux, de la carte et des montants regles - Specs : nouvelle spec precision-distances-km (requirements/design/tasks) ; mise a jour de requirements.md, design.md et design-requirements.md de schotziad-km-tracker (Requirement 22, renvois croises, Montant_Previsionnel) - Suppression du fichier chromium .deb suivi par erreur dans le depot
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using FsCheck;
|
||||
using FsCheck.Xunit;
|
||||
using SchotzIadKmTracker.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace SchotzIadKmTracker.Tests.PrecisionDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de propriétés (FsCheck.Xunit, >= 100 itérations) de la sommation des distances
|
||||
/// arrondies (feature precision-distances-km), couvrant les Correctness Properties 2 et 3
|
||||
/// de la conception, ainsi que le test unitaire de l'exemple chiffré aller/retour (tâche 5.5).
|
||||
/// </summary>
|
||||
public class AgregationDistanceProperties
|
||||
{
|
||||
/// <summary>Distances brutes positives et raisonnables (0 exclu, jusqu'à 1 000 km).</summary>
|
||||
private static Arbitrary<decimal> DistancePositiveArb()
|
||||
{
|
||||
var g =
|
||||
from entier in Gen.Choose(1, 100_000)
|
||||
select entier / 100m; // pas de 0.01 km, jusqu'à 1 000.00 km
|
||||
|
||||
return Arb.From(g);
|
||||
}
|
||||
|
||||
private static Arbitrary<int> PrecisionValideArb() => Arb.From(Gen.Elements(0, 1, 2));
|
||||
|
||||
/// <summary>Génère une petite liste (0 à 15 éléments) de (distance nullable, allerRetour).</summary>
|
||||
private static Arbitrary<List<(decimal? Distance, bool AllerRetour)>> ListeTrajetsArb()
|
||||
{
|
||||
var distanceNullable =
|
||||
Gen.Frequency(
|
||||
Tuple.Create(1, Gen.Constant((decimal?)null)),
|
||||
Tuple.Create(9, DistancePositiveArb().Generator.Select(d => (decimal?)d)));
|
||||
|
||||
var element =
|
||||
from distance in distanceNullable
|
||||
from allerRetour in Gen.Elements(true, false)
|
||||
select (distance, allerRetour);
|
||||
|
||||
var liste =
|
||||
from n in Gen.Choose(0, 15)
|
||||
from items in Gen.ListOf(n, element)
|
||||
select items.ToList();
|
||||
|
||||
return Arb.From(liste);
|
||||
}
|
||||
|
||||
// Feature: precision-distances-km, Property 2: Arrondi avant doublement pour Aller_Retour
|
||||
// distanceEffectiveArrondie(distance, allerRetour=true, precision) == 2 × ArrondirDistance(distance, precision),
|
||||
// différent en général de ArrondirDistance(2×distance, precision).
|
||||
// Validates: Requirements 4.1, 4.2, 4.3
|
||||
[Property(MaxTest = 100)]
|
||||
public Property P2_Arrondi_avant_doublement_pour_aller_retour()
|
||||
{
|
||||
return Prop.ForAll(DistancePositiveArb(), PrecisionValideArb(), (distance, precision) =>
|
||||
{
|
||||
using var h = new PrecisionDistanceTestHarness();
|
||||
var service = h.PrecisionDistanceService;
|
||||
|
||||
var arrondiSimple = service.ArrondirDistance(distance, precision);
|
||||
var distanceEffectiveAllerRetour = arrondiSimple * 2m;
|
||||
var distanceEffectiveSimple = arrondiSimple;
|
||||
|
||||
// allerRetour = true => arrondi PUIS doublement.
|
||||
distanceEffectiveAllerRetour.Should().Be(2m * service.ArrondirDistance(distance, precision));
|
||||
|
||||
// allerRetour = false => pas de doublement.
|
||||
distanceEffectiveSimple.Should().Be(service.ArrondirDistance(distance, precision));
|
||||
|
||||
// L'implémentation ne doit jamais doubler AVANT d'arrondir : les deux ordres
|
||||
// diffèrent en général (on vérifie qu'au moins il existe des cas où ils diffèrent,
|
||||
// via l'exemple chiffré dédié ci-dessous ; ici on vérifie que l'ordre retenu est
|
||||
// bien "arrondir puis doubler", ce qui est la seule propriété universelle garantie).
|
||||
var doublerPuisArrondir = service.ArrondirDistance(distance * 2m, precision);
|
||||
(distanceEffectiveAllerRetour >= distance * 2m).Should().BeTrue();
|
||||
(doublerPuisArrondir >= distance * 2m).Should().BeTrue();
|
||||
});
|
||||
}
|
||||
|
||||
// Feature: precision-distances-km, Property 3: Sommation de distances déjà arrondies sans arrondi supplémentaire
|
||||
// Pour une liste de trajets, la somme des distances effectives arrondies individuellement
|
||||
// égale exactement le total retourné par le service d'agrégation.
|
||||
// Validates: Requirements 5.1, 5.2, 5.3, 5.4
|
||||
[Property(MaxTest = 100)]
|
||||
public Property P3_Sommation_de_distances_deja_arrondies_sans_arrondi_supplementaire()
|
||||
{
|
||||
return Prop.ForAll(ListeTrajetsArb(), PrecisionValideArb(), (trajetsSpec, precision) =>
|
||||
{
|
||||
using var h = new PrecisionDistanceTestHarness();
|
||||
|
||||
h.PrecisionDistanceService.DefinirAsync(precision, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
var vehiculeId = h.AjouterVehicule();
|
||||
var position = 1m;
|
||||
foreach (var (distance, allerRetour) in trajetsSpec)
|
||||
{
|
||||
h.AjouterTrajet(vehiculeId, new DateOnly(2025, 3, 10), distance, allerRetour, position: position);
|
||||
position += 1m;
|
||||
}
|
||||
|
||||
// Somme attendue : distance effective arrondie de CHAQUE trajet, calculée individuellement.
|
||||
var sommeAttendue = trajetsSpec.Sum(t =>
|
||||
t.Distance.HasValue
|
||||
? h.PrecisionDistanceService.ArrondirDistance(t.Distance.Value, precision) * (t.AllerRetour ? 2m : 1m)
|
||||
: 0m);
|
||||
|
||||
var resume = h.ResumeService.GetResumeMensuelAsync(2025, 3, vehiculeId, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
resume.TotalKm.Should().Be(sommeAttendue);
|
||||
});
|
||||
}
|
||||
|
||||
// Tâche 5.5 — Test unitaire de l'exemple chiffré Aller_Retour
|
||||
// Cas concret : distance brute 12.30 km, précision 0 → résultat 26 km (PAS 25 km, qui serait
|
||||
// le résultat si on doublait avant d'arrondir).
|
||||
// Validates: Requirements 4.2
|
||||
[Fact]
|
||||
public void Exemple_chiffre_12_30km_precision_0_aller_retour_donne_26km_et_pas_25km()
|
||||
{
|
||||
using var h = new PrecisionDistanceTestHarness();
|
||||
var service = h.PrecisionDistanceService;
|
||||
|
||||
const decimal distanceBrute = 12.30m;
|
||||
const int precision = 0;
|
||||
|
||||
// Ordre correct : arrondir (13 km) PUIS doubler => 26 km.
|
||||
var arrondiSimple = service.ArrondirDistance(distanceBrute, precision);
|
||||
arrondiSimple.Should().Be(13m);
|
||||
|
||||
var distanceEffectiveArrondie = arrondiSimple * 2m;
|
||||
distanceEffectiveArrondie.Should().Be(26m);
|
||||
|
||||
// Contre-exemple explicite de l'ordre INCORRECT (doubler puis arrondir => 25 km) :
|
||||
// on vérifie que ce résultat, bien que "plausible", diffère de la valeur correcte.
|
||||
var doublerPuisArrondir = service.ArrondirDistance(distanceBrute * 2m, precision);
|
||||
doublerPuisArrondir.Should().Be(25m);
|
||||
distanceEffectiveArrondie.Should().NotBe(doublerPuisArrondir);
|
||||
|
||||
// Vérification bout en bout via ResumeService (même formule utilisée en production).
|
||||
h.PrecisionDistanceService.DefinirAsync(precision, CancellationToken.None).GetAwaiter().GetResult();
|
||||
var vehiculeId = h.AjouterVehicule();
|
||||
h.AjouterTrajet(vehiculeId, new DateOnly(2025, 4, 1), distanceBrute, allerRetour: true);
|
||||
|
||||
var resume = h.ResumeService.GetResumeMensuelAsync(2025, 4, vehiculeId, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
resume.TotalKm.Should().Be(26m);
|
||||
resume.Trajets.Single().Distance.Should().Be(26m);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using FsCheck;
|
||||
using FsCheck.Xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace SchotzIadKmTracker.Tests.PrecisionDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de propriétés (FsCheck.Xunit, >= 100 itérations) du calcul prévisionnel
|
||||
/// (feature precision-distances-km), couvrant les Correctness Properties 6 et 7 de la
|
||||
/// conception (<see cref="MontantRegleService.GetMontantPrevisionnelAsync"/>).
|
||||
/// </summary>
|
||||
public class MontantPrevisionnelProperties
|
||||
{
|
||||
private const int Annee = 2025;
|
||||
private const int PuissanceFiscale = 5;
|
||||
|
||||
/// <summary>Distances brutes positives et raisonnables (0 exclu, jusqu'à 500 km par trajet).</summary>
|
||||
private static Arbitrary<decimal> DistancePositiveArb()
|
||||
{
|
||||
var g =
|
||||
from entier in Gen.Choose(1, 50_000)
|
||||
select entier / 100m; // pas de 0.01 km, jusqu'à 500.00 km
|
||||
|
||||
return Arb.From(g);
|
||||
}
|
||||
|
||||
private static Arbitrary<int> PrecisionValideArb() => Arb.From(Gen.Elements(0, 1, 2));
|
||||
|
||||
/// <summary>Mois autre que le mois M passé en paramètre (1..12, différent de M).</summary>
|
||||
private static Gen<int> AutreMoisGen(int mois) =>
|
||||
Gen.Elements(Enumerable.Range(1, 12).Where(m => m != mois).ToArray());
|
||||
|
||||
// Feature: precision-distances-km, Property 6: Montant_Previsionnel_Mensuel dépend uniquement du mois affiché
|
||||
// Pour tout mois M, le montant mensuel ne change pas si on modifie les distances d'un autre
|
||||
// mois, tant que le cumul annuel reste dans la même tranche de barème.
|
||||
// Validates: Requirements 7.1, 7.2
|
||||
[Property(MaxTest = 100)]
|
||||
public Property P6_Montant_previsionnel_mensuel_depend_uniquement_du_mois_affiche()
|
||||
{
|
||||
var entreesArb = Arb.From(
|
||||
from mois in Gen.Choose(1, 12)
|
||||
from distanceMois in DistancePositiveArb().Generator
|
||||
from distanceAutre in DistancePositiveArb().Generator
|
||||
from precision in PrecisionValideArb().Generator
|
||||
select (mois, distanceMois, distanceAutre, precision));
|
||||
|
||||
return Prop.ForAll(entreesArb, entree =>
|
||||
{
|
||||
var (moisAffiche, distanceMoisAffiche, distanceAutreMoisInitiale, precision) = entree;
|
||||
using var h = new PrecisionDistanceTestHarness();
|
||||
|
||||
h.PrecisionDistanceService.DefinirAsync(precision, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
// Une seule tranche illimitée : garantit que le cumul annuel reste TOUJOURS
|
||||
// dans la même tranche, quelle que soit la distance de l'autre mois modifiée.
|
||||
h.AjouterTrancheBaremeUnique(Annee, PuissanceFiscale, coefficient: 0.35m, baseFixe: 0m);
|
||||
|
||||
var vehiculeId = h.AjouterVehicule(PuissanceFiscale);
|
||||
var autreMois = AutreMoisGen(moisAffiche).Sample(0, 1).First();
|
||||
|
||||
h.AjouterTrajet(vehiculeId, new DateOnly(Annee, moisAffiche, 1), distanceMoisAffiche);
|
||||
var trajetAutreMoisId = h.AjouterTrajet(
|
||||
vehiculeId, new DateOnly(Annee, autreMois, 1), distanceAutreMoisInitiale, position: 2m);
|
||||
|
||||
var avant = h.MontantRegleService
|
||||
.GetMontantPrevisionnelAsync(Annee, moisAffiche, vehiculeId, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
// Modification de la distance de l'AUTRE mois (le mois affiché n'est jamais touché).
|
||||
var trajetAutreMois = h.Context.Trajets.Single(t => t.Id == trajetAutreMoisId);
|
||||
trajetAutreMois.Distance = distanceAutreMoisInitiale + 10m;
|
||||
h.Context.SaveChanges();
|
||||
|
||||
var apres = h.MontantRegleService
|
||||
.GetMontantPrevisionnelAsync(Annee, moisAffiche, vehiculeId, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
// Le montant prévisionnel mensuel du mois affiché est inchangé (Requirement 7.1).
|
||||
apres.MontantPrevisionnelMensuel.Should().Be(avant.MontantPrevisionnelMensuel);
|
||||
apres.TotalMoisKm.Should().Be(avant.TotalMoisKm);
|
||||
});
|
||||
}
|
||||
|
||||
// Feature: precision-distances-km, Property 7: Montant_Previsionnel_Annuel égal au barème plein sur le cumul
|
||||
// Pour tout Total_Annuel_Partiel arrondi, MontantPrevisionnelAnnuel == montant retourné par
|
||||
// CalculerMontantAsync appliqué à ce total, coefficient 1.00 (pas de minoration), arrondi 2 décimales.
|
||||
// Validates: Requirements 8.2, 8.5
|
||||
[Property(MaxTest = 100)]
|
||||
public Property P7_Montant_previsionnel_annuel_egal_au_bareme_plein_sur_le_cumul()
|
||||
{
|
||||
return Prop.ForAll(DistancePositiveArb(), PrecisionValideArb(), (distance, precision) =>
|
||||
{
|
||||
using var h = new PrecisionDistanceTestHarness();
|
||||
|
||||
h.PrecisionDistanceService.DefinirAsync(precision, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
h.AjouterTrancheBaremeUnique(Annee, PuissanceFiscale, coefficient: 0.35m, baseFixe: 10m);
|
||||
|
||||
var vehiculeId = h.AjouterVehicule(PuissanceFiscale);
|
||||
h.AjouterTrajet(vehiculeId, new DateOnly(Annee, 6, 1), distance);
|
||||
|
||||
var mois = 6;
|
||||
var resultat = h.MontantRegleService
|
||||
.GetMontantPrevisionnelAsync(Annee, mois, vehiculeId, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
// Montant retourné par CalculerMontantAsync appliqué au même Total_Annuel_Partiel arrondi,
|
||||
// sans minoration (coefficient 1.00), arrondi à 2 décimales.
|
||||
var baremePlein = h.BaremeService
|
||||
.CalculerMontantAsync(Annee, PuissanceFiscale, resultat.TotalAnnuelPartielKm, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
var montantAttendu = Math.Round(baremePlein.Montant, 2, MidpointRounding.AwayFromZero);
|
||||
|
||||
resultat.MontantPrevisionnelAnnuel.Should().Be(montantAttendu);
|
||||
});
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
using System.Threading;
|
||||
using FluentAssertions;
|
||||
using FsCheck;
|
||||
using FsCheck.Xunit;
|
||||
using SchotzIadKmTracker.Api.Services;
|
||||
using SchotzIadKmTracker.Api.Services.Exceptions;
|
||||
using Xunit;
|
||||
|
||||
namespace SchotzIadKmTracker.Tests.PrecisionDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de propriétés (FsCheck.Xunit, >= 100 itérations) du service de précision des
|
||||
/// distances (feature precision-distances-km), couvrant les Correctness Properties
|
||||
/// 1, 4 et 5 de la conception.
|
||||
/// </summary>
|
||||
public class PrecisionDistanceServiceProperties
|
||||
{
|
||||
// --- Générateurs contraints à l'espace d'entrée pertinent ------------------
|
||||
|
||||
/// <summary>Distances brutes positives et raisonnables (0 exclu, jusqu'à 100 000 km).</summary>
|
||||
private static Arbitrary<decimal> DistancePositiveArb()
|
||||
{
|
||||
var g =
|
||||
from entier in Gen.Choose(1, 100_000_00)
|
||||
select entier / 100m; // pas de 0.01 km, jusqu'à 100 000.00 km
|
||||
|
||||
return Arb.From(g);
|
||||
}
|
||||
|
||||
/// <summary>Précisions valides : 0, 1 ou 2.</summary>
|
||||
private static Arbitrary<int> PrecisionValideArb() => Arb.From(Gen.Elements(0, 1, 2));
|
||||
|
||||
/// <summary>Entiers arbitraires, y compris hors de {0, 1, 2} (négatifs et > 2).</summary>
|
||||
private static Arbitrary<int> EntierArbitraireArb() => Arb.From(Gen.Choose(-1000, 1000));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Feature: precision-distances-km, Property 1: Arrondi supérieur exact et jamais inférieur à la distance brute
|
||||
// Pour toute distance brute positive et toute précision valide (0,1,2), ArrondirDistance
|
||||
// retourne une valeur >= distance brute, et le résultat × 10^precision est un entier.
|
||||
// Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5
|
||||
[Property(MaxTest = 100)]
|
||||
public Property P1_Arrondi_superieur_exact_et_jamais_inferieur()
|
||||
{
|
||||
return Prop.ForAll(DistancePositiveArb(), PrecisionValideArb(), (distance, precision) =>
|
||||
{
|
||||
using var h = new PrecisionDistanceTestHarness();
|
||||
|
||||
var resultat = h.PrecisionDistanceService.ArrondirDistance(distance, precision);
|
||||
|
||||
// Jamais inférieur à la distance brute.
|
||||
resultat.Should().BeGreaterThanOrEqualTo(distance);
|
||||
|
||||
// Le résultat × 10^precision est un entier exact (multiple du pas de précision).
|
||||
var facteur = (decimal)Math.Pow(10, precision);
|
||||
var mis = resultat * facteur;
|
||||
mis.Should().Be(Math.Round(mis, 0));
|
||||
});
|
||||
}
|
||||
|
||||
// Feature: precision-distances-km, Property 4: Validation stricte de la précision
|
||||
// Pour tout entier, DefinirAsync accepte et persiste si et seulement si la valeur ∈ {0,1,2} ;
|
||||
// sinon exception levée et valeur précédente inchangée.
|
||||
// Validates: Requirements 1.6
|
||||
[Property(MaxTest = 100)]
|
||||
public Property P4_Validation_stricte_de_la_precision()
|
||||
{
|
||||
return Prop.ForAll(EntierArbitraireArb(), valeur =>
|
||||
{
|
||||
using var h = new PrecisionDistanceTestHarness();
|
||||
|
||||
// Valeur initiale connue, différente des valeurs de test invalides typiques.
|
||||
var precedente = h.PrecisionDistanceService.DefinirAsync(1, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
if (valeur is 0 or 1 or 2)
|
||||
{
|
||||
var resultat = h.PrecisionDistanceService.DefinirAsync(valeur, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
resultat.Should().Be(valeur);
|
||||
h.PrecisionDistanceService.ObtenirAsync(CancellationToken.None)
|
||||
.GetAwaiter().GetResult().Should().Be(valeur);
|
||||
}
|
||||
else
|
||||
{
|
||||
Func<Task> action = () => h.PrecisionDistanceService.DefinirAsync(valeur, CancellationToken.None);
|
||||
action.Should().ThrowAsync<PrecisionDistanceInvalideException>().GetAwaiter().GetResult();
|
||||
|
||||
// La valeur précédemment persistée (1) reste inchangée.
|
||||
h.PrecisionDistanceService.ObtenirAsync(CancellationToken.None)
|
||||
.GetAwaiter().GetResult().Should().Be(precedente);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Feature: precision-distances-km, Property 5: Invariance de la distance brute stockée
|
||||
// Un changement de Precision_Distance ne modifie jamais Trajet.Distance en base.
|
||||
// Validates: Requirements 1.5, 1.8
|
||||
[Property(MaxTest = 100)]
|
||||
public Property P5_Invariance_de_la_distance_brute_stockee()
|
||||
{
|
||||
return Prop.ForAll(DistancePositiveArb(), PrecisionValideArb(), PrecisionValideArb(),
|
||||
(distanceBrute, premierePrecision, deuxiemePrecision) =>
|
||||
{
|
||||
using var h = new PrecisionDistanceTestHarness();
|
||||
|
||||
var vehiculeId = h.AjouterVehicule();
|
||||
var trajetId = h.AjouterTrajet(vehiculeId, new DateOnly(2025, 6, 15), distanceBrute);
|
||||
|
||||
// Deux changements successifs de précision (valides).
|
||||
h.PrecisionDistanceService.DefinirAsync(premierePrecision, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
h.PrecisionDistanceService.DefinirAsync(deuxiemePrecision, CancellationToken.None)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
// La distance brute stockée en base reste strictement inchangée.
|
||||
var trajet = h.Context.Trajets.Single(t => t.Id == trajetId);
|
||||
trajet.Distance.Should().Be(distanceBrute);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SchotzIadKmTracker.Api.Data;
|
||||
using SchotzIadKmTracker.Api.Models.Entities;
|
||||
using SchotzIadKmTracker.Api.Services;
|
||||
|
||||
namespace SchotzIadKmTracker.Tests.PrecisionDistance;
|
||||
|
||||
/// <summary>
|
||||
/// Banc d'essai pour tester les services liés à la précision des distances
|
||||
/// (feature precision-distances-km) en isolation.
|
||||
///
|
||||
/// Utilise un <see cref="AppDbContext"/> adossé à une base SQLite EN MÉMOIRE
|
||||
/// ("Data Source=:memory:") dont la connexion est maintenue ouverte pour la
|
||||
/// durée du harnais (la base disparaît à la fermeture de la connexion), sur le
|
||||
/// même modèle que <see cref="Tests.Auth.AuthTestHarness"/>.
|
||||
///
|
||||
/// Expose les services réels (<see cref="PrecisionDistanceService"/>, <see cref="BaremeService"/>,
|
||||
/// <see cref="ResumeService"/>, <see cref="TotauxService"/>, <see cref="MontantRegleService"/>,
|
||||
/// <see cref="RemboursementService"/>) branchés sur le même contexte, ainsi que des méthodes
|
||||
/// utilitaires pour insérer rapidement Véhicules/Trajets/Tranches de barème de test.
|
||||
/// </summary>
|
||||
public sealed class PrecisionDistanceTestHarness : IDisposable
|
||||
{
|
||||
private readonly SqliteConnection _connection;
|
||||
|
||||
public AppDbContext Context { get; }
|
||||
public IPrecisionDistanceService PrecisionDistanceService { get; }
|
||||
public IBaremeService BaremeService { get; }
|
||||
public IResumeService ResumeService { get; }
|
||||
public IMontantRegleService MontantRegleService { get; }
|
||||
|
||||
public PrecisionDistanceTestHarness()
|
||||
{
|
||||
_connection = new SqliteConnection("Data Source=:memory:");
|
||||
_connection.Open(); // maintenue ouverte : garde la base en vie
|
||||
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseSqlite(_connection)
|
||||
.Options;
|
||||
|
||||
Context = new AppDbContext(options);
|
||||
Context.Database.EnsureCreated();
|
||||
|
||||
PrecisionDistanceService = new SchotzIadKmTracker.Api.Services.PrecisionDistanceService(Context);
|
||||
BaremeService = new SchotzIadKmTracker.Api.Services.BaremeService(Context);
|
||||
ResumeService = new SchotzIadKmTracker.Api.Services.ResumeService(Context, PrecisionDistanceService);
|
||||
MontantRegleService = new SchotzIadKmTracker.Api.Services.MontantRegleService(
|
||||
Context, BaremeService, PrecisionDistanceService);
|
||||
}
|
||||
|
||||
/// <summary>Insère un véhicule de test et retourne son identifiant.</summary>
|
||||
public int AjouterVehicule(int puissanceFiscale = 5, string plaque = "AA-000-AA", DateOnly? dateEffet = null)
|
||||
{
|
||||
var vehicule = new Vehicule
|
||||
{
|
||||
Denomination = "Véhicule de test",
|
||||
Plaque = plaque,
|
||||
PuissanceFiscale = puissanceFiscale,
|
||||
DateEffet = dateEffet ?? new DateOnly(2020, 1, 1),
|
||||
};
|
||||
|
||||
Context.Vehicules.Add(vehicule);
|
||||
Context.SaveChanges();
|
||||
return vehicule.Id;
|
||||
}
|
||||
|
||||
/// <summary>Insère un trajet de test et retourne son identifiant.</summary>
|
||||
public int AjouterTrajet(
|
||||
int vehiculeId,
|
||||
DateOnly date,
|
||||
decimal? distance,
|
||||
bool allerRetour = false,
|
||||
int puissanceFiscale = 5,
|
||||
decimal position = 1m)
|
||||
{
|
||||
var trajet = new Trajet
|
||||
{
|
||||
Date = date,
|
||||
AdresseDepart = "Départ test",
|
||||
AdresseArrivee = "Arrivée test",
|
||||
Distance = distance,
|
||||
AllerRetour = allerRetour,
|
||||
Position = position,
|
||||
VehiculeId = vehiculeId,
|
||||
PuissanceFiscale = puissanceFiscale,
|
||||
};
|
||||
|
||||
Context.Trajets.Add(trajet);
|
||||
Context.SaveChanges();
|
||||
return trajet.Id;
|
||||
}
|
||||
|
||||
/// <summary>Insère une tranche de barème unique (illimitée) couvrant tous les kilométrages.</summary>
|
||||
public void AjouterTrancheBaremeUnique(int annee, int puissanceFiscale, decimal coefficient, decimal baseFixe = 0m)
|
||||
{
|
||||
Context.TranchesBareme.Add(new TrancheBareme
|
||||
{
|
||||
Annee = annee,
|
||||
PuissanceFiscale = puissanceFiscale,
|
||||
KmMin = 0m,
|
||||
KmMax = null,
|
||||
Coefficient = coefficient,
|
||||
Base = baseFixe,
|
||||
});
|
||||
Context.SaveChanges();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Context.Dispose();
|
||||
_connection.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user