1576 lines
65 KiB
C#
1576 lines
65 KiB
C#
using Omika.Core.Compatibility;
|
|
using Omika.Core.Contracts.Clients;
|
|
using Omika.Core.Contracts.Common;
|
|
using Omika.Core.Contracts.Configuration;
|
|
using Omika.Core.Contracts.Documents;
|
|
using Omika.Core.Contracts.Interventions;
|
|
using Omika.Core.Contracts.Mobile.Documents;
|
|
using Omika.Core.Contracts.Mobile.Snapshot;
|
|
using Omika.Core.Contracts.Organisation;
|
|
using Omika.Core.Contracts.Planning;
|
|
using Omika.Core.Contracts.Mobile.Sync;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Omika.Core.Contracts.Sites;
|
|
using Omika.Core.Interfaces;
|
|
using Omika.Core.Interfaces.Mobile;
|
|
using Omika.Core.Services.Mobile;
|
|
|
|
|
|
|
|
using Omika.Core.Domain;
|
|
|
|
namespace Omika.Tests.Mobile;
|
|
|
|
/// <summary>
|
|
/// Tests du service de snapshot mobile.
|
|
/// </summary>
|
|
public class MobileSnapshotServiceTests
|
|
{
|
|
private static MobileSnapshotService CreateService(
|
|
IClientService? clients = null,
|
|
ICollaborateurService? collaborateurs = null,
|
|
ISiteService? sites = null,
|
|
IInterventionService? interventions = null,
|
|
IDossierService? dossiers = null,
|
|
IRendezVousService? rendezVous = null,
|
|
IMobileDocumentService? documents = null,
|
|
ISuiviTempsService? suivisTemps = null,
|
|
IParametreMetierRepository? parametres = null)
|
|
{
|
|
return new MobileSnapshotService(
|
|
clients ?? new FakeClientService(),
|
|
collaborateurs ?? new FakeCollaborateurService(),
|
|
sites ?? new FakeSiteService(),
|
|
interventions ?? new FakeInterventionService(),
|
|
dossiers ?? new FakeDossierService(),
|
|
rendezVous ?? new FakeRendezVousService(),
|
|
documents ?? new FakeMobileDocumentService(),
|
|
suivisTemps ?? new FakeSuiviTempsService(),
|
|
parametres ?? new FakeParametreMetierRepository(),
|
|
new FakeIdentityResolver(),
|
|
NullLogger<MobileSnapshotService>.Instance);
|
|
|
|
}
|
|
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Returns_Complete_Contract()
|
|
{
|
|
// Arrange
|
|
var service = CreateService();
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
// Assert : données obligatoires présentes.
|
|
Assert.NotEqual(default, result.GeneratedAtUtc);
|
|
Assert.NotNull(result.Clients);
|
|
Assert.NotNull(result.Personnes);
|
|
Assert.NotNull(result.Sites);
|
|
Assert.NotNull(result.Documents);
|
|
Assert.NotNull(result.Interventions);
|
|
Assert.NotNull(result.Dossiers);
|
|
Assert.NotNull(result.RendezVous);
|
|
Assert.NotNull(result.Collaborateurs);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Contains_No_Document_Buffer()
|
|
{
|
|
// Arrange
|
|
var service = CreateService();
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
// Assert : le snapshot ne contient que des métadonnées de documents.
|
|
// DocumentSummaryDto ne porte aucun contenu binaire (pas de buffer).
|
|
Assert.All(result.Documents, d => Assert.Equal(0, d.TailleOctets));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Is_Identical_For_Batik_And_Omika()
|
|
{
|
|
// Arrange
|
|
var service = CreateService();
|
|
|
|
// Act
|
|
var batik = await service.GetSnapshotAsync("user-1", "device-1");
|
|
var omika = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
// Assert : le contrat public est identique quelle que soit la génération.
|
|
Assert.IsType<MobileSnapshotResponse>(batik);
|
|
Assert.IsType<MobileSnapshotResponse>(omika);
|
|
Assert.Equal(batik.Clients.Count, omika.Clients.Count);
|
|
Assert.Equal(batik.Documents.Count, omika.Documents.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Respects_CancellationToken()
|
|
{
|
|
// Arrange
|
|
var service = CreateService();
|
|
using var cts = new CancellationTokenSource();
|
|
cts.Cancel();
|
|
|
|
// Act & Assert : l'annulation est propagée.
|
|
await Assert.ThrowsAnyAsync<OperationCanceledException>(
|
|
() => service.GetSnapshotAsync("user-1", "device-1", null, cancellationToken: cts.Token));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Empty_UserId_Throws()
|
|
{
|
|
// Arrange
|
|
var service = CreateService();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(
|
|
() => service.GetSnapshotAsync("", "device-1"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Empty_DeviceId_Throws()
|
|
{
|
|
// Arrange
|
|
var service = CreateService();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(
|
|
() => service.GetSnapshotAsync("user-1", ""));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_No_SuivisTemps_Returns_Empty_Collection()
|
|
{
|
|
// Arrange : aucun SuiviTemps (fake par défaut vide).
|
|
var service = CreateService();
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
// Assert : collection vide (pas null) et autres sections inchangées.
|
|
Assert.NotNull(result.SuivisTemps);
|
|
Assert.Empty(result.SuivisTemps);
|
|
Assert.NotNull(result.Clients);
|
|
Assert.NotNull(result.Interventions);
|
|
Assert.NotNull(result.RendezVous);
|
|
Assert.NotNull(result.Collaborateurs);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Load_SuivisTemps()
|
|
{
|
|
// Le snapshot ne charge plus les saisies de temps en bloc : elles sont
|
|
// récupérées à la demande via GET /api/suivis-temps.
|
|
var fake = new FakeSuiviTempsService(new[]
|
|
{
|
|
new SuiviTempsDto { Id = Guid.NewGuid().ToString("D"), DureeMinutes = 45 }
|
|
});
|
|
var service = CreateService(suivisTemps: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.SuivisTemps);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_SuiviTemps_Not_Exposed()
|
|
{
|
|
// Les saisies de temps ne transitent plus par le snapshot : identité
|
|
// physique ou synthétique, aucune n'est embarquée.
|
|
var fake = new FakeSuiviTempsService(new[]
|
|
{
|
|
new SuiviTempsDto { Id = Guid.NewGuid().ToString("D") }
|
|
});
|
|
var service = CreateService(suivisTemps: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.SuivisTemps);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_SuiviTemps_Exposes_No_HfSql_Columns()
|
|
{
|
|
// Arrange
|
|
var fake = new FakeSuiviTempsService(new[]
|
|
{
|
|
new SuiviTempsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
CollaborateurId = "aaaaaaaa-0000-0000-0000-000000000001",
|
|
InterventionId = "aaaaaaaa-0000-0000-0000-000000000002",
|
|
RendezVousId = "aaaaaaaa-0000-0000-0000-000000000003",
|
|
Date = DateTime.UtcNow,
|
|
DureeMinutes = 30,
|
|
TypeTemps = new ReferenceValueDto { Key = "SAISIE", Label = "Saisie" },
|
|
Description = "Test",
|
|
Valide = false
|
|
}
|
|
});
|
|
var service = CreateService(suivisTemps: fake);
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
string json = System.Text.Json.JsonSerializer.Serialize(result);
|
|
|
|
// Assert : aucune colonne physique HFSQL n'est exposée dans le contrat public.
|
|
Assert.DoesNotContain("SDT_PKID", json);
|
|
Assert.DoesNotContain("SDT_IDCollaborateur", json);
|
|
Assert.DoesNotContain("SDT_IDIntervention", json);
|
|
Assert.DoesNotContain("SDT_IDRdv", json);
|
|
Assert.DoesNotContain("SDT_NbMinutes", json);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Load_Clients()
|
|
{
|
|
// Le snapshot ne charge plus les clients en bloc : ils sont récupérés à la
|
|
// demande (liste via GET /clients, détail via GET /clients/{id}).
|
|
var fake = new FakeClientService(
|
|
details:
|
|
[
|
|
new ClientDetailsDto { Id = Guid.NewGuid().ToString("D"), Nom = "Client X" }
|
|
]);
|
|
|
|
var service = CreateService(clients: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Clients);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Load_Sites()
|
|
{
|
|
// Le snapshot ne charge plus les sites en bloc : ils sont récupérés à la
|
|
// demande (liste via GET /sites, détail via GET /sites/{id}).
|
|
var fake = new FakeSiteService(
|
|
details:
|
|
[
|
|
new SiteDetailsDto { Id = Guid.NewGuid().ToString("D"), Nom = "Site X" }
|
|
]);
|
|
|
|
var service = CreateService(sites: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Sites);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Load_Site_Details()
|
|
{
|
|
// Les détails de sites ne sont plus embarqués dans le snapshot.
|
|
var fake = new FakeSiteService(
|
|
details:
|
|
[
|
|
new SiteDetailsDto { Id = Guid.NewGuid().ToString("D"), Nom = "Site 42" }
|
|
]);
|
|
|
|
var service = CreateService(sites: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Sites);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_Does_Not_Call_GetById_Per_RendezVous()
|
|
{
|
|
// Arrange : 100 summaries. Le snapshot doit charger les rendez-vous
|
|
// COMPLETS via SearchDetailsAsync (bulk) et ne JAMAIS appeler
|
|
// GetByIdAsync par rendez-vous. C'est le test anti-N+1 : il échoue avec
|
|
// l'ancien pattern (SearchAsync + GetByIdAsync par summary).
|
|
const int count = 100;
|
|
const string collaborateurId = "aaaaaaaa-0000-0000-0000-0000000000ff";
|
|
var summaries = Enumerable.Range(1, count)
|
|
.Select(i => new RendezVousSummaryDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
SourceReference = LegacyEntityReference.FromLegacyId(i)
|
|
})
|
|
.ToList();
|
|
|
|
var details = summaries
|
|
.Select(s => new RendezVousDetailsDto
|
|
{
|
|
Id = s.Id,
|
|
SourceReference = s.SourceReference,
|
|
TypeActivite = MobileEnabledTypeRdv(),
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = collaborateurId,
|
|
PeriodeDebut = DateTime.Today.AddHours(9),
|
|
PeriodeFin = DateTime.Today.AddHours(10)
|
|
}
|
|
]
|
|
})
|
|
.ToList();
|
|
|
|
var getByIdCalls = 0;
|
|
var fake = new FakeRendezVousService(
|
|
summaries,
|
|
details,
|
|
id =>
|
|
{
|
|
getByIdCalls++;
|
|
return null;
|
|
});
|
|
|
|
var service = CreateService(rendezVous: fake);
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", collaborateurId);
|
|
|
|
// Assert : tous les rendez-vous sont présents (chargés en bulk) et aucun
|
|
// GetByIdAsync n'a été appelé. Le N+1 est éliminé.
|
|
Assert.Equal(count, result.RendezVous.Count);
|
|
Assert.Equal(0, getByIdCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Load_Collaborateurs()
|
|
{
|
|
// Le snapshot ne charge plus les collaborateurs en bloc : ils sont
|
|
// récupérés à la demande.
|
|
var fake = new FakeCollaborateurService(
|
|
details:
|
|
[
|
|
new CollaborateurDetailsDto { Id = Guid.NewGuid().ToString("D"), Nom = "Doe" }
|
|
]);
|
|
|
|
var service = CreateService(collaborateurs: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Collaborateurs);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Load_Collaborateur_Details()
|
|
{
|
|
// Les détails de collaborateurs ne sont plus embarqués dans le snapshot.
|
|
var fake = new FakeCollaborateurService(
|
|
details:
|
|
[
|
|
new CollaborateurDetailsDto { Id = Guid.NewGuid().ToString("D"), Nom = "Doe" }
|
|
]);
|
|
|
|
var service = CreateService(collaborateurs: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Collaborateurs);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Load_Dossiers()
|
|
{
|
|
// Le snapshot ne charge plus les dossiers en bloc : ils sont récupérés à
|
|
// la demande (recherche via GET /dossiers, détail via GET /dossiers/{id}).
|
|
var fake = new FakeDossierService(
|
|
details:
|
|
[
|
|
new DossierDetailsDto { Id = Guid.NewGuid().ToString("D"), Titre = "Dossier X" }
|
|
]);
|
|
|
|
var service = CreateService(dossiers: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Dossiers);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Load_Dossier_Details()
|
|
{
|
|
// Les détails de dossiers ne sont plus embarqués dans le snapshot.
|
|
var fake = new FakeDossierService(
|
|
details:
|
|
[
|
|
new DossierDetailsDto { Id = Guid.NewGuid().ToString("D"), Titre = "Dossier 42" }
|
|
]);
|
|
|
|
var service = CreateService(dossiers: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Dossiers);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Interventions_Empty_NoGlobalLoad()
|
|
{
|
|
// Règle « strictement à la demande » : sans rendez-vous, le snapshot ne
|
|
// charge aucune intervention (aucun SearchDetailsAsync, aucune collection
|
|
// globale). Les interventions ne sont chargées que si des rendez-vous les
|
|
// référencent (voir Snapshot_Loads_Interventions_For_Agenda_RendezVous).
|
|
var fake = new FakeInterventionService(details:
|
|
[
|
|
new InterventionDetailsDto { Id = Guid.NewGuid().ToString("D"), Titre = "X" }
|
|
]);
|
|
|
|
var service = CreateService(interventions: fake);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Interventions);
|
|
Assert.Equal(0, fake.SearchDetailsCallCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Loads_Interventions_For_Agenda_RendezVous()
|
|
{
|
|
// Arrange : un rendez-vous lié à une intervention (PK numérique + GUID
|
|
// physique Omika, comme le mapper OmikaRendezVousMapper les produit).
|
|
const long interventionPk = 123L;
|
|
Guid interventionGuid = Guid.Parse("22222222-1111-0000-0000-000000000001");
|
|
const string collaborateurId = "aaaaaaaa-0000-0000-0000-0000000000ff";
|
|
|
|
var details = new[]
|
|
{
|
|
new RendezVousDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Titre = "RDV intervention",
|
|
TypeActivite = MobileEnabledTypeRdv(),
|
|
InterventionSourceReference = new LegacyEntityReference(
|
|
DatabaseSchemaVersion.Omika, NumericId: interventionPk, EntityGuid: interventionGuid),
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = collaborateurId,
|
|
PeriodeDebut = DateTime.Today.AddHours(9),
|
|
PeriodeFin = DateTime.Today.AddHours(10)
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
var interventionFake = new FakeInterventionService(details:
|
|
[
|
|
new InterventionDetailsDto
|
|
{
|
|
Id = interventionGuid.ToString("D"),
|
|
SourceReference = new LegacyEntityReference(
|
|
DatabaseSchemaVersion.Omika, NumericId: interventionPk, EntityGuid: interventionGuid),
|
|
Reference = "INT-001",
|
|
NumeroDevis = "DEV-42",
|
|
ReferenceExterne = "EXT-7"
|
|
}
|
|
]);
|
|
|
|
var service = CreateService(
|
|
rendezVous: new FakeRendezVousService(details: details),
|
|
interventions: interventionFake);
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", collaborateurId);
|
|
|
|
// Assert : l'intervention liée est chargée en bulk ciblé (IN_PKID IN (...)),
|
|
// jamais via un chargement global ni un GetById par intervention.
|
|
var intervention = Assert.Single(result.Interventions);
|
|
Assert.Equal(interventionGuid.ToString("D"), intervention.Id);
|
|
Assert.Equal("INT-001", intervention.Reference);
|
|
Assert.Equal("DEV-42", intervention.NumeroDevis);
|
|
Assert.Equal("EXT-7", intervention.ReferenceExterne);
|
|
Assert.Equal(1, interventionFake.SearchDetailsCallCount);
|
|
Assert.NotNull(interventionFake.LastSearchRequest);
|
|
Assert.NotNull(interventionFake.LastSearchRequest.Ids);
|
|
Assert.Contains(interventionFake.LastSearchRequest.Ids, id => id == "123");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Loads_Notes_Emails_Documents_For_Agenda_Interventions()
|
|
{
|
|
// Arrange : un rendez-vous lié à une intervention (PK numérique + GUID
|
|
// physique). Le snapshot doit charger en bulk les notes (internes +
|
|
// administratives), les courriels et les métadonnées de documents.
|
|
const long interventionPk = 123L;
|
|
Guid interventionGuid = Guid.Parse("22222222-1111-0000-0000-000000000001");
|
|
const string collaborateurId = "aaaaaaaa-0000-0000-0000-0000000000ff";
|
|
|
|
var details = new[]
|
|
{
|
|
new RendezVousDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Titre = "RDV intervention",
|
|
TypeActivite = MobileEnabledTypeRdv(),
|
|
InterventionSourceReference = new LegacyEntityReference(
|
|
DatabaseSchemaVersion.Omika, NumericId: interventionPk, EntityGuid: interventionGuid),
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = collaborateurId,
|
|
PeriodeDebut = DateTime.Today.AddHours(9),
|
|
PeriodeFin = DateTime.Today.AddHours(10)
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
var interventionFake = new FakeInterventionService(
|
|
details:
|
|
[
|
|
new InterventionDetailsDto
|
|
{
|
|
Id = interventionGuid.ToString("D"),
|
|
SourceReference = new LegacyEntityReference(
|
|
DatabaseSchemaVersion.Omika, NumericId: interventionPk, EntityGuid: interventionGuid),
|
|
Reference = "INT-001"
|
|
}
|
|
],
|
|
bulkNotes:
|
|
[
|
|
new InterventionNoteBulkRow
|
|
{
|
|
InterventionNumericId = interventionPk,
|
|
EstAdministrative = false,
|
|
Note = new NoteInterneDto { Id = Guid.NewGuid().ToString("D"), Contenu = "Note interne", DateCreation = DateTime.Today }
|
|
},
|
|
new InterventionNoteBulkRow
|
|
{
|
|
InterventionNumericId = interventionPk,
|
|
EstAdministrative = true,
|
|
Note = new NoteInterneDto { Id = Guid.NewGuid().ToString("D"), Contenu = "Note admin", DateCreation = DateTime.Today }
|
|
}
|
|
],
|
|
bulkEmails:
|
|
[
|
|
new InterventionEmailBulkRow
|
|
{
|
|
InterventionNumericId = interventionPk,
|
|
Email = new EmailInterventionDto { Id = Guid.NewGuid().ToString("D"), Objet = "Sujet", DateCreation = DateTime.Today }
|
|
}
|
|
]);
|
|
|
|
var documentId = Guid.NewGuid().ToString("D");
|
|
var documentFake = new FakeMobileDocumentService(
|
|
[
|
|
new DocumentSummaryDto { Id = documentId, InterventionId = interventionGuid.ToString("D"), Nom = "fichier.pdf" }
|
|
]);
|
|
|
|
var service = CreateService(
|
|
rendezVous: new FakeRendezVousService(details: details),
|
|
interventions: interventionFake,
|
|
documents: documentFake);
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", collaborateurId, includeNotesAdministratives: true);
|
|
|
|
// Assert : notes internes + administratives, courriel et document chargés.
|
|
Assert.Equal(2, result.Notes.Count);
|
|
Assert.Contains(result.Notes, n => n.EstAdministrative && n.Contenu == "Note admin" && n.InterventionId == interventionGuid.ToString("D"));
|
|
Assert.Contains(result.Notes, n => !n.EstAdministrative && n.Contenu == "Note interne");
|
|
|
|
var email = Assert.Single(result.Emails);
|
|
Assert.Equal("Sujet", email.Objet);
|
|
Assert.Equal(interventionGuid.ToString("D"), email.InterventionId);
|
|
|
|
var document = Assert.Single(result.Documents);
|
|
Assert.Equal(documentId, document.Id);
|
|
Assert.Equal(interventionGuid.ToString("D"), document.InterventionId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Excludes_Administrative_Notes_Without_Permission()
|
|
{
|
|
// Arrange : notes interne + administrative, mais le droit notes admin
|
|
// n'est pas accordé → la note administrative est exclue du snapshot.
|
|
const long interventionPk = 123L;
|
|
Guid interventionGuid = Guid.Parse("22222222-1111-0000-0000-000000000001");
|
|
const string collaborateurId = "aaaaaaaa-0000-0000-0000-0000000000ff";
|
|
|
|
var details = new[]
|
|
{
|
|
new RendezVousDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Titre = "RDV",
|
|
TypeActivite = MobileEnabledTypeRdv(),
|
|
InterventionSourceReference = new LegacyEntityReference(
|
|
DatabaseSchemaVersion.Omika, NumericId: interventionPk, EntityGuid: interventionGuid),
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = collaborateurId,
|
|
PeriodeDebut = DateTime.Today.AddHours(9),
|
|
PeriodeFin = DateTime.Today.AddHours(10)
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
var interventionFake = new FakeInterventionService(
|
|
details:
|
|
[
|
|
new InterventionDetailsDto
|
|
{
|
|
Id = interventionGuid.ToString("D"),
|
|
SourceReference = new LegacyEntityReference(
|
|
DatabaseSchemaVersion.Omika, NumericId: interventionPk, EntityGuid: interventionGuid)
|
|
}
|
|
],
|
|
bulkNotes:
|
|
[
|
|
new InterventionNoteBulkRow
|
|
{
|
|
InterventionNumericId = interventionPk,
|
|
EstAdministrative = false,
|
|
Note = new NoteInterneDto { Id = Guid.NewGuid().ToString("D"), Contenu = "Note interne" }
|
|
},
|
|
new InterventionNoteBulkRow
|
|
{
|
|
InterventionNumericId = interventionPk,
|
|
EstAdministrative = true,
|
|
Note = new NoteInterneDto { Id = Guid.NewGuid().ToString("D"), Contenu = "Note admin" }
|
|
}
|
|
]);
|
|
|
|
var service = CreateService(
|
|
rendezVous: new FakeRendezVousService(details: details),
|
|
interventions: interventionFake);
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", collaborateurId, includeNotesAdministratives: false);
|
|
|
|
// Assert : seule la note interne est conservée.
|
|
var note = Assert.Single(result.Notes);
|
|
Assert.False(note.EstAdministrative);
|
|
Assert.Equal("Note interne", note.Contenu);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Attach_Dossier_Counts()
|
|
{
|
|
// Le compteur d'interventions par dossier n'est plus calculé dans le
|
|
// snapshot : les dossiers sont chargés à la demande (le compteur, s'il est
|
|
// nécessaire, est résolu par l'endpoint de détail/recherche de dossier).
|
|
var dossiers = new FakeDossierService(details:
|
|
[
|
|
new DossierDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
SourceReference = LegacyEntityReference.FromLegacyId(123),
|
|
Titre = "Dossier A"
|
|
}
|
|
])
|
|
{
|
|
Counts = new Dictionary<long, int> { [123] = 2 }
|
|
};
|
|
|
|
var service = CreateService(dossiers: dossiers);
|
|
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Dossiers);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_Details_Preserve_Contract_Properties()
|
|
|
|
{
|
|
|
|
// Arrange : un rendez-vous détail complet avec les propriétés
|
|
// contractuelles attendues (SourceReference, ClientSourceReference,
|
|
// SiteSourceReference, DossierSourceReference, InterventionSourceReference).
|
|
var sourceRef = LegacyEntityReference.FromLegacyId(42);
|
|
var clientRef = LegacyEntityReference.FromLegacyId(7);
|
|
var siteRef = LegacyEntityReference.FromLegacyId(9);
|
|
var dossierRef = LegacyEntityReference.FromLegacyId(11);
|
|
var interventionRef = LegacyEntityReference.FromLegacyId(13);
|
|
const string collaborateurId = "aaaaaaaa-0000-0000-0000-0000000000ff";
|
|
var details = new[]
|
|
{
|
|
new RendezVousDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
TypeActivite = MobileEnabledTypeRdv(),
|
|
SourceReference = sourceRef,
|
|
ClientSourceReference = clientRef,
|
|
SiteSourceReference = siteRef,
|
|
DossierSourceReference = dossierRef,
|
|
InterventionSourceReference = interventionRef,
|
|
Titre = "Réunion client",
|
|
Description = "Point d'avancement",
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = collaborateurId,
|
|
PeriodeDebut = DateTime.Today.AddHours(9),
|
|
PeriodeFin = DateTime.Today.AddHours(10)
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
var fake = new FakeRendezVousService(details: details);
|
|
var service = CreateService(rendezVous: fake);
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", collaborateurId);
|
|
|
|
// Assert : le rendez-vous détail conserve toutes les propriétés
|
|
// contractuelles attendues après le mapping d'identité du snapshot.
|
|
var rdv = Assert.Single(result.RendezVous);
|
|
Assert.Equal("Réunion client", rdv.Titre);
|
|
Assert.Equal("Point d'avancement", rdv.Description);
|
|
Assert.NotNull(rdv.SourceReference);
|
|
Assert.Equal(42, rdv.SourceReference.NumericId);
|
|
Assert.NotNull(rdv.ClientSourceReference);
|
|
Assert.Equal(7, rdv.ClientSourceReference.NumericId);
|
|
Assert.NotNull(rdv.SiteSourceReference);
|
|
Assert.Equal(9, rdv.SiteSourceReference.NumericId);
|
|
Assert.NotNull(rdv.DossierSourceReference);
|
|
Assert.Equal(11, rdv.DossierSourceReference.NumericId);
|
|
Assert.NotNull(rdv.InterventionSourceReference);
|
|
Assert.Equal(13, rdv.InterventionSourceReference.NumericId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Derive_Personnes_From_Clients()
|
|
{
|
|
// Les contacts de clients ne sont plus embarqués dans le snapshot : ils
|
|
// sont portés par le détail client récupéré à la demande. Sans
|
|
// intervention, aucune Personne n'est émise.
|
|
var clientA = new ClientDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Nom = "Client A",
|
|
Contacts = [new ContactDto { Id = Guid.NewGuid().ToString("D"), Nom = "Dupont" }]
|
|
};
|
|
|
|
var fake = new FakeClientService(details: [clientA]);
|
|
var service = CreateService(clients: fake);
|
|
|
|
MobileSnapshotResponse result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Personnes);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_Does_Not_Preserve_Client_Personnes()
|
|
{
|
|
// Les Personnes de clients ne sont plus embarquées (clients chargés à la
|
|
// demande). Sans intervention, la collection Personnes reste vide.
|
|
var client = new ClientDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Nom = "Client",
|
|
Contacts =
|
|
[
|
|
new ContactDto { Id = Guid.NewGuid().ToString("D"), Nom = "Dupont" },
|
|
new ContactDto { Id = Guid.NewGuid().ToString("D"), Nom = "Dupont" }
|
|
]
|
|
};
|
|
|
|
var fake = new FakeClientService(details: [client]);
|
|
var service = CreateService(clients: fake);
|
|
|
|
MobileSnapshotResponse result = await service.GetSnapshotAsync("user-1", "device-1");
|
|
|
|
Assert.Empty(result.Personnes);
|
|
}
|
|
|
|
[Fact]
|
|
public void Diagnostic_Personne_Ids_Uniqueness_Counts()
|
|
{
|
|
// Arrange : un snapshot en mémoire avec un doublon d'Id (diagnostic ciblé).
|
|
Guid uniqueA = Guid.NewGuid();
|
|
Guid uniqueB = Guid.NewGuid();
|
|
Guid duplicate = Guid.NewGuid();
|
|
|
|
var snapshot = new MobileSnapshotResponse
|
|
{
|
|
GeneratedAtUtc = DateTimeOffset.UtcNow,
|
|
Personnes =
|
|
[
|
|
new ContactDto { Id = uniqueA.ToString("D") },
|
|
new ContactDto { Id = uniqueB.ToString("D") },
|
|
new ContactDto { Id = duplicate.ToString("D") },
|
|
new ContactDto { Id = duplicate.ToString("D") }
|
|
]
|
|
};
|
|
|
|
// Act : compter les Id distincts sans exposer aucune donnée personnelle.
|
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
int duplicates = 0;
|
|
foreach (ContactDto personne in snapshot.Personnes)
|
|
{
|
|
if (!seen.Add(personne.Id))
|
|
{
|
|
duplicates++;
|
|
}
|
|
}
|
|
|
|
int total = snapshot.Personnes.Count;
|
|
int distinct = seen.Count;
|
|
Console.WriteLine($"PERSONNE IDS total={total} distinct={distinct} duplicates={duplicates}");
|
|
|
|
// Assert
|
|
Assert.Equal(4, total);
|
|
Assert.Equal(3, distinct);
|
|
Assert.Equal(1, duplicates);
|
|
}
|
|
|
|
// ===== Filtrage des RendezVous par Collaborateur =====
|
|
|
|
private const string CollaborateurA = "aaaaaaaa-0000-0000-0000-0000000000a1";
|
|
private const string CollaborateurB = "bbbbbbbb-0000-0000-0000-0000000000b2";
|
|
|
|
private const string TypeRdvMobileKey = "T.RDV.INTER";
|
|
private const string TypeRdvMobileLabel = "Intervention";
|
|
|
|
private static ReferenceValueDto MobileEnabledTypeRdv()
|
|
=> new() { Key = TypeRdvMobileKey, Label = TypeRdvMobileLabel };
|
|
|
|
private static ParametreMetierDto MobileEnabledTypeRdvParam()
|
|
=> new()
|
|
{
|
|
Id = TypeRdvMobileKey,
|
|
Code = "14",
|
|
Categorie = "T.RDV",
|
|
Libelle = TypeRdvMobileLabel,
|
|
Parametre = """{"Envoi_mobile":1}"""
|
|
};
|
|
|
|
private static RendezVousDetailsDto CreateRdv(string titre, params string?[] collaborateurIds)
|
|
=> new()
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Titre = titre,
|
|
TypeActivite = MobileEnabledTypeRdv(),
|
|
Collaborateurs = collaborateurIds
|
|
.Select(c => new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = c,
|
|
PeriodeDebut = DateTime.Today.AddHours(9),
|
|
PeriodeFin = DateTime.Today.AddHours(10)
|
|
})
|
|
.ToList()
|
|
};
|
|
|
|
private static RendezVousDetailsDto[] FourRendezVous()
|
|
=>
|
|
[
|
|
CreateRdv("RDV1", CollaborateurA),
|
|
CreateRdv("RDV2", CollaborateurB),
|
|
CreateRdv("RDV3", CollaborateurA, CollaborateurB),
|
|
CreateRdv("RDV4")
|
|
];
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_FilteredByCollaborateurA()
|
|
{
|
|
// Arrange : RDV1 → A, RDV2 → B, RDV3 → A+B, RDV4 → aucun.
|
|
var service = CreateService(rendezVous: new FakeRendezVousService(details: FourRendezVous()));
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", CollaborateurA);
|
|
|
|
// Assert : uniquement RDV1 et RDV3.
|
|
Assert.Equal(2, result.RendezVous.Count);
|
|
Assert.Contains(result.RendezVous, r => r.Titre == "RDV1");
|
|
Assert.Contains(result.RendezVous, r => r.Titre == "RDV3");
|
|
Assert.DoesNotContain(result.RendezVous, r => r.Titre == "RDV2");
|
|
Assert.DoesNotContain(result.RendezVous, r => r.Titre == "RDV4");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_FilteredByCollaborateurB()
|
|
{
|
|
// Arrange
|
|
var service = CreateService(rendezVous: new FakeRendezVousService(details: FourRendezVous()));
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", CollaborateurB);
|
|
|
|
// Assert : uniquement RDV2 et RDV3.
|
|
Assert.Equal(2, result.RendezVous.Count);
|
|
Assert.Contains(result.RendezVous, r => r.Titre == "RDV2");
|
|
Assert.Contains(result.RendezVous, r => r.Titre == "RDV3");
|
|
Assert.DoesNotContain(result.RendezVous, r => r.Titre == "RDV1");
|
|
Assert.DoesNotContain(result.RendezVous, r => r.Titre == "RDV4");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_CollaborateurNull_ReturnsEmpty_OtherCollectionsIntact()
|
|
{
|
|
// Arrange : un rendez-vous et un client présents (le client n'est plus
|
|
// embarqué dans le snapshot : chargé à la demande).
|
|
var clients = new[]
|
|
{
|
|
new ClientDetailsDto { Id = Guid.NewGuid().ToString("D"), Nom = "Client X" }
|
|
};
|
|
var service = CreateService(
|
|
rendezVous: new FakeRendezVousService(details: FourRendezVous()),
|
|
clients: new FakeClientService(details: clients));
|
|
|
|
// Act : collaborateur null.
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", null);
|
|
|
|
// Assert : 0 RendezVous ; les clients ne sont pas embarqués (à la demande).
|
|
Assert.Empty(result.RendezVous);
|
|
Assert.Empty(result.Clients);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_MultiCollaborateur_NoDuplicate()
|
|
{
|
|
// Arrange : RDV3 affecté à A + B. Pour A, il ne doit apparaître qu'une fois.
|
|
var service = CreateService(rendezVous: new FakeRendezVousService(details: FourRendezVous()));
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", CollaborateurA);
|
|
|
|
// Assert : RDV3 présent une seule fois (pas de doublon).
|
|
Assert.Single(result.RendezVous, r => r.Titre == "RDV3");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_SansCollaborateur_Exclu()
|
|
{
|
|
// Arrange : RDV4 sans affectation + RDV1 affecté à A.
|
|
var service = CreateService(rendezVous: new FakeRendezVousService(details: FourRendezVous()));
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", CollaborateurA);
|
|
|
|
// Assert : RDV4 (sans collaborateur) est exclu.
|
|
Assert.DoesNotContain(result.RendezVous, r => r.Titre == "RDV4");
|
|
Assert.Contains(result.RendezVous, r => r.Titre == "RDV1");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_CollaborateurInconnu_ReturnsEmpty()
|
|
{
|
|
// Arrange : aucun rendez-vous affecté à "collaborateur-inconnu".
|
|
var service = CreateService(rendezVous: new FakeRendezVousService(details: FourRendezVous()));
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", "cccccccc-0000-0000-0000-0000000000c3");
|
|
|
|
// Assert : aucun RendezVous (pas de retombée vers "tous").
|
|
Assert.Empty(result.RendezVous);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_ResolvesNumericCollaborateurId_ToCanonicalGuid()
|
|
{
|
|
// Arrange : le token porte le PK numérique de la licence (LI_IDCollaborateur),
|
|
// alors que les affectations portent le GUID canonique. Le snapshot doit
|
|
// résoudre le PK numérique vers le GUID canonique AVANT de filtrer, sinon
|
|
// aucun rendez-vous n'est jamais remonté (bug offline « semaine suivante »).
|
|
const string collaborateurGuid = "aaaaaaaa-0000-0000-0000-000000000001";
|
|
var collaborateurs = new FakeCollaborateurService(
|
|
getById: _ => new CollaborateurDetailsDto { Id = collaborateurGuid, Nom = "Doe" });
|
|
|
|
var rdvs = new[] { CreateRdv("RDV1", collaborateurGuid) };
|
|
|
|
var service = CreateService(
|
|
collaborateurs: collaborateurs,
|
|
rendezVous: new FakeRendezVousService(details: rdvs));
|
|
|
|
// Act : le token porte "42" (PK numérique), pas le GUID.
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", "42");
|
|
|
|
// Assert : le rendez-vous est bien remonté (PK numérique → GUID canonique).
|
|
Assert.Single(result.RendezVous);
|
|
Assert.Contains(result.RendezVous, r => r.Titre == "RDV1");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_DebutFin_Provient_De_LAffectation()
|
|
{
|
|
// Arrange : la racine porte une date (RV_Date) différente de la date de
|
|
// l'affectation du collaborateur (RVC_DateDeb / RVC_DateFin). Le snapshot
|
|
// doit rabattre Debut/Fin sur la date de l'affectation.
|
|
var details = new[]
|
|
{
|
|
new RendezVousDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Titre = "RDV affectation",
|
|
TypeActivite = MobileEnabledTypeRdv(),
|
|
Debut = DateTime.Today.AddDays(-30),
|
|
Fin = DateTime.Today.AddDays(-30).AddHours(1),
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = CollaborateurA,
|
|
PeriodeDebut = DateTime.Today.AddHours(9),
|
|
PeriodeFin = DateTime.Today.AddHours(10)
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
var service = CreateService(rendezVous: new FakeRendezVousService(details: details));
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", CollaborateurA);
|
|
|
|
// Assert : Debut/Fin proviennent de l'affectation, pas de la racine.
|
|
var rdv = Assert.Single(result.RendezVous);
|
|
Assert.Equal(DateTime.Today.AddHours(9), rdv.Debut);
|
|
Assert.Equal(DateTime.Today.AddHours(10), rdv.Fin);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_FilteredByEnvoiMobile()
|
|
{
|
|
// Arrange : un type « Intervention » marqué Envoi_mobile et un type
|
|
// « Absence » non marqué. Seul le rendez-vous de type mobile est envoyé.
|
|
var parametres = new FakeParametreMetierRepository(
|
|
[
|
|
MobileEnabledTypeRdvParam(),
|
|
new ParametreMetierDto
|
|
{
|
|
Id = "T.RDV.ABSENCE",
|
|
Code = "14",
|
|
Categorie = "T.RDV",
|
|
Libelle = "Absence",
|
|
Parametre = """{"Envoi_mobile":0}"""
|
|
}
|
|
]);
|
|
|
|
var details = new[]
|
|
{
|
|
CreateRdv("RDV intervention", CollaborateurA),
|
|
new RendezVousDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Titre = "RDV absence",
|
|
TypeActivite = new ReferenceValueDto { Key = "T.RDV.ABSENCE", Label = "Absence" },
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = CollaborateurA,
|
|
PeriodeDebut = DateTime.Today.AddHours(11),
|
|
PeriodeFin = DateTime.Today.AddHours(12)
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
var service = CreateService(
|
|
rendezVous: new FakeRendezVousService(details: details),
|
|
parametres: parametres);
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", CollaborateurA);
|
|
|
|
// Assert : seul le RDV de type mobile est conservé.
|
|
var rdv = Assert.Single(result.RendezVous);
|
|
Assert.Equal("RDV intervention", rdv.Titre);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVous_SansTypeActivite_Exclu()
|
|
{
|
|
// Arrange : un rendez-vous sans type d'activité ne peut pas être rattaché
|
|
// à une règle Envoi_mobile : il est exclu du mobile.
|
|
var details = new[]
|
|
{
|
|
new RendezVousDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Titre = "RDV sans type",
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = CollaborateurA,
|
|
PeriodeDebut = DateTime.Today.AddHours(9),
|
|
PeriodeFin = DateTime.Today.AddHours(10)
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
var service = CreateService(rendezVous: new FakeRendezVousService(details: details));
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", CollaborateurA);
|
|
|
|
// Assert
|
|
Assert.Empty(result.RendezVous);
|
|
}
|
|
|
|
|
|
[Fact]
|
|
public async Task Snapshot_RendezVousPlusieursJours_PeriodeEnveloppeDuCollaborateur()
|
|
{
|
|
// Arrange : rendez-vous groupé sur plusieurs jours. Le collaborateur A
|
|
// porte TROIS affectations (une par jour), transmises dans le désordre —
|
|
// la base ne garantit aucun ordre. La période envoyée au mobile doit être
|
|
// l'enveloppe (premier début, dernière fin), sinon le rendez-vous
|
|
// n'apparaît que sur une journée arbitraire de son intervalle.
|
|
DateTime premierJour = DateTime.Today.AddDays(-2);
|
|
DateTime dernierJour = DateTime.Today;
|
|
var rdv = new RendezVousDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString("D"),
|
|
Titre = "Chantier 3 jours",
|
|
TypeActivite = MobileEnabledTypeRdv(),
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = CollaborateurA,
|
|
PeriodeDebut = dernierJour.AddHours(12),
|
|
PeriodeFin = dernierJour.AddHours(15)
|
|
},
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = CollaborateurB,
|
|
PeriodeDebut = premierJour.AddHours(12),
|
|
PeriodeFin = premierJour.AddHours(15)
|
|
},
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = CollaborateurA,
|
|
PeriodeDebut = premierJour.AddHours(12),
|
|
PeriodeFin = premierJour.AddHours(15)
|
|
},
|
|
new AffectationCollaborateurDto
|
|
{
|
|
CollaborateurId = CollaborateurA,
|
|
PeriodeDebut = premierJour.AddDays(1).AddHours(12),
|
|
PeriodeFin = premierJour.AddDays(1).AddHours(15)
|
|
}
|
|
]
|
|
};
|
|
|
|
var service = CreateService(rendezVous: new FakeRendezVousService(details: [rdv]));
|
|
|
|
// Act
|
|
var result = await service.GetSnapshotAsync("user-1", "device-1", CollaborateurA);
|
|
|
|
// Assert : premier jour 12:00 → dernier jour 15:00.
|
|
RendezVousDetailsDto envoye = Assert.Single(result.RendezVous);
|
|
Assert.Equal(premierJour.AddHours(12), envoye.Debut);
|
|
Assert.Equal(dernierJour.AddHours(15), envoye.Fin);
|
|
}
|
|
|
|
// ===== Fakes =====
|
|
|
|
|
|
|
|
private sealed class FakeIdentityResolver : IMobileEntityIdentityResolver
|
|
|
|
{
|
|
public Guid CreateCanonicalIdentity(string entityType, PhysicalEntityIdentity physical)
|
|
=> physical.Guid ?? Guid.NewGuid();
|
|
|
|
public PhysicalEntityIdentity? ResolvePhysicalIdentity(string entityType, Guid publicGuid)
|
|
=> new() { Guid = publicGuid };
|
|
|
|
public bool IsCanonical(string entityType, Guid publicGuid)
|
|
=> publicGuid != Guid.Empty;
|
|
}
|
|
|
|
private sealed class FakeClientService : IClientService
|
|
|
|
{
|
|
private readonly IReadOnlyList<ClientSummaryDto> _items;
|
|
private readonly IReadOnlyList<ClientDetailsDto> _details;
|
|
private readonly Func<string, ClientDetailsDto?>? _getById;
|
|
|
|
public FakeClientService(
|
|
IReadOnlyList<ClientSummaryDto>? items = null,
|
|
IReadOnlyList<ClientDetailsDto>? details = null,
|
|
Func<string, ClientDetailsDto?>? getById = null)
|
|
{
|
|
_items = items ?? Array.Empty<ClientSummaryDto>();
|
|
_details = details ?? Array.Empty<ClientDetailsDto>();
|
|
_getById = getById;
|
|
}
|
|
|
|
public Task<PagedResult<ClientSummaryDto>> SearchAsync(SearchClientsRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<ClientSummaryDto>(_items, 1, 1000, _items.Count, 1));
|
|
}
|
|
|
|
public Task<PagedResult<ClientDetailsDto>> SearchDetailsAsync(SearchClientsRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<ClientDetailsDto>(_details, 1, 1000, _details.Count, 1));
|
|
}
|
|
|
|
public Task<ClientDetailsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_getById is null ? null : _getById(id));
|
|
|
|
|
|
|
|
public Task<ClientDetailsDto> CreateAsync(CreateClientRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<ClientDetailsDto?> UpdateAsync(string id, UpdateClientRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
|
|
private sealed class FakeCollaborateurService : ICollaborateurService
|
|
{
|
|
private readonly IReadOnlyList<CollaborateurSummaryDto> _items;
|
|
private readonly IReadOnlyList<CollaborateurDetailsDto> _details;
|
|
private readonly Func<string, CollaborateurDetailsDto?>? _getById;
|
|
|
|
public FakeCollaborateurService(
|
|
IReadOnlyList<CollaborateurSummaryDto>? items = null,
|
|
IReadOnlyList<CollaborateurDetailsDto>? details = null,
|
|
Func<string, CollaborateurDetailsDto?>? getById = null)
|
|
{
|
|
_items = items ?? Array.Empty<CollaborateurSummaryDto>();
|
|
_details = details ?? Array.Empty<CollaborateurDetailsDto>();
|
|
_getById = getById;
|
|
}
|
|
|
|
public Task<PagedResult<CollaborateurSummaryDto>> SearchAsync(SearchCollaborateurRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<CollaborateurSummaryDto>(_items, 1, 1000, _items.Count, 1));
|
|
}
|
|
|
|
public Task<PagedResult<CollaborateurDetailsDto>> SearchDetailsAsync(SearchCollaborateurRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<CollaborateurDetailsDto>(_details, 1, 1000, _details.Count, 1));
|
|
}
|
|
|
|
public Task<CollaborateurDetailsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_getById is null ? null : _getById(id));
|
|
|
|
public Task<CollaborateurDetailsDto> CreateAsync(CreateCollaborateurRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<CollaborateurDetailsDto> UpdateAsync(string id, UpdateCollaborateurRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<IReadOnlyList<RendezVousDetailsDto>> GetRendezVousAsync(string collaborateurId, DateTime debutMin, DateTime debutMax, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<RendezVousDetailsDto>>([]);
|
|
|
|
public Task<IReadOnlyList<RendezVousDetailsDto>> GetRendezVousForCollaborateursAsync(IReadOnlyList<string> collaborateurIds, DateTime debutMin, DateTime debutMax, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<RendezVousDetailsDto>>([]);
|
|
|
|
public Task<IReadOnlyList<long>> GetSocieteIdsAsync(long collaborateurId, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<long>>([]);
|
|
|
|
public Task<IReadOnlyList<long>> GetAllowedCollaborateurIdsAsync(long collaborateurId, bool includeSocietePrincipale, bool includeSocietes, bool includeEquipe, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<long>>([]);
|
|
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
|
|
|
|
private sealed class FakeSiteService : ISiteService
|
|
{
|
|
private readonly IReadOnlyList<SiteSummaryDto> _items;
|
|
private readonly IReadOnlyList<SiteDetailsDto> _details;
|
|
private readonly Func<string, SiteDetailsDto?>? _getById;
|
|
|
|
public FakeSiteService(
|
|
IReadOnlyList<SiteSummaryDto>? items = null,
|
|
IReadOnlyList<SiteDetailsDto>? details = null,
|
|
Func<string, SiteDetailsDto?>? getById = null)
|
|
{
|
|
_items = items ?? Array.Empty<SiteSummaryDto>();
|
|
_details = details ?? Array.Empty<SiteDetailsDto>();
|
|
_getById = getById;
|
|
}
|
|
|
|
public Task<PagedResult<SiteSummaryDto>> SearchAsync(SearchSitesRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<SiteSummaryDto>(_items, 1, 1000, _items.Count, 1));
|
|
}
|
|
|
|
public Task<PagedResult<SiteDetailsDto>> SearchDetailsAsync(SearchSitesRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<SiteDetailsDto>(_details, 1, 1000, _details.Count, 1));
|
|
}
|
|
|
|
public Task<SiteDetailsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_getById is null ? null : _getById(id));
|
|
|
|
public Task<IReadOnlyList<ContactDto>> GetContactsAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<ContactDto>>([]);
|
|
|
|
public Task<IReadOnlyList<RendezVousDetailsDto>> GetRendezVousAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<RendezVousDetailsDto>>([]);
|
|
|
|
public Task<IReadOnlyList<ClientSummaryDto>> GetClientsAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<ClientSummaryDto>>([]);
|
|
|
|
public Task<SiteDetailsDto> CreateAsync(CreateSiteRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<SiteDetailsDto?> UpdateAsync(string id, UpdateSiteRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
|
|
|
|
private sealed class FakeInterventionService : IInterventionService
|
|
{
|
|
private readonly IReadOnlyList<InterventionDetailsDto> _details;
|
|
private readonly Func<string, InterventionDetailsDto?>? _getById;
|
|
private readonly IReadOnlyList<InterventionNoteBulkRow> _bulkNotes;
|
|
private readonly IReadOnlyList<InterventionEmailBulkRow> _bulkEmails;
|
|
|
|
public FakeInterventionService(
|
|
IReadOnlyList<InterventionDetailsDto>? details = null,
|
|
Func<string, InterventionDetailsDto?>? getById = null,
|
|
IReadOnlyList<InterventionNoteBulkRow>? bulkNotes = null,
|
|
IReadOnlyList<InterventionEmailBulkRow>? bulkEmails = null)
|
|
{
|
|
_details = details ?? Array.Empty<InterventionDetailsDto>();
|
|
_getById = getById;
|
|
_bulkNotes = bulkNotes ?? Array.Empty<InterventionNoteBulkRow>();
|
|
_bulkEmails = bulkEmails ?? Array.Empty<InterventionEmailBulkRow>();
|
|
}
|
|
|
|
/// <summary>Nombre d'appels au chargement bulk d'interventions (SearchDetailsAsync).</summary>
|
|
public int SearchDetailsCallCount { get; private set; }
|
|
|
|
/// <summary>Dernière requête reçue par SearchDetailsAsync (pour vérifier le filtre ciblé).</summary>
|
|
public SearchInterventionsRequest? LastSearchRequest { get; private set; }
|
|
|
|
public Task<PagedResult<InterventionSummaryDto>> SearchAsync(SearchInterventionsRequest request, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(new PagedResult<InterventionSummaryDto>(Array.Empty<InterventionSummaryDto>(), 1, 1000, 0, 1));
|
|
|
|
public Task<PagedResult<InterventionDetailsDto>> SearchDetailsAsync(SearchInterventionsRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
SearchDetailsCallCount++;
|
|
LastSearchRequest = request;
|
|
return Task.FromResult(new PagedResult<InterventionDetailsDto>(_details, 1, 1000, _details.Count, 1));
|
|
}
|
|
|
|
public Task<InterventionDetailsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_getById?.Invoke(id));
|
|
|
|
public Task<InterventionDetailsDto> CreateAsync(CreateInterventionRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<InterventionDetailsDto?> UpdateAsync(string id, UpdateInterventionRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<IReadOnlyList<NoteInterneDto>> GetNotesAsync(string interventionId, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<NoteInterneDto>>([]);
|
|
|
|
public Task<IReadOnlyList<NoteInterneDto>> GetNotesAdministrativesAsync(string interventionId, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<NoteInterneDto>>([]);
|
|
|
|
public Task<NoteInterneDto> AddNoteAsync(string interventionId, string contenu, AuditActor? auteur, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(new NoteInterneDto());
|
|
|
|
public Task<IReadOnlyList<EmailInterventionDto>> GetEmailsAsync(string interventionId, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<EmailInterventionDto>>([]);
|
|
|
|
public Task<IReadOnlyList<InterventionNoteBulkRow>> GetNotesForInterventionsAsync(
|
|
IReadOnlyList<long> numericInterventionIds, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_bulkNotes);
|
|
|
|
public Task<IReadOnlyList<InterventionEmailBulkRow>> GetEmailsForInterventionsAsync(
|
|
IReadOnlyList<long> numericInterventionIds, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_bulkEmails);
|
|
|
|
public Task<EmailInterventionDto> AddEmailAsync(string interventionId, CreateEmailInterventionRequest request, AuditActor? auteur, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(new EmailInterventionDto());
|
|
}
|
|
|
|
|
|
private sealed class FakeDossierService : IDossierService
|
|
{
|
|
private readonly IReadOnlyList<DossierSummaryDto> _items;
|
|
private readonly IReadOnlyList<DossierDetailsDto> _details;
|
|
private readonly Func<string, DossierDetailsDto?>? _getById;
|
|
|
|
public FakeDossierService(
|
|
IReadOnlyList<DossierSummaryDto>? items = null,
|
|
IReadOnlyList<DossierDetailsDto>? details = null,
|
|
Func<string, DossierDetailsDto?>? getById = null)
|
|
{
|
|
_items = items ?? Array.Empty<DossierSummaryDto>();
|
|
_details = details ?? Array.Empty<DossierDetailsDto>();
|
|
_getById = getById;
|
|
}
|
|
|
|
/// <summary>Compteurs d'interventions non archivées (PK numérique → count).</summary>
|
|
public IReadOnlyDictionary<long, int> Counts { get; set; } = new Dictionary<long, int>();
|
|
|
|
public Task<PagedResult<DossierSummaryDto>> SearchAsync(SearchDossiersRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<DossierSummaryDto>(_items, 1, 1000, _items.Count, 1));
|
|
}
|
|
|
|
public Task<PagedResult<DossierDetailsDto>> SearchDetailsAsync(SearchDossiersRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<DossierDetailsDto>(_details, 1, 1000, _details.Count, 1));
|
|
}
|
|
|
|
public Task<DossierDetailsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_getById is null ? null : _getById(id));
|
|
|
|
public Task<DossierDetailsDto> CreateAsync(CreateDossierRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<DossierDetailsDto?> UpdateAsync(string id, UpdateDossierRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<IReadOnlyDictionary<long, int>> GetNonArchivedInterventionCountsAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(Counts);
|
|
}
|
|
|
|
private sealed class FakeParametreMetierRepository : IParametreMetierRepository
|
|
{
|
|
private readonly IReadOnlyList<ParametreMetierDto> _items;
|
|
|
|
public FakeParametreMetierRepository(IReadOnlyList<ParametreMetierDto>? items = null)
|
|
=> _items = items ?? [MobileEnabledTypeRdvParam()];
|
|
|
|
public Task<IReadOnlyList<ParametreMetierDto>> GetAllAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(_items);
|
|
}
|
|
}
|
|
|
|
private sealed class FakeRendezVousService : IRendezVousService
|
|
{
|
|
private readonly IReadOnlyList<RendezVousSummaryDto> _items;
|
|
private readonly IReadOnlyList<RendezVousDetailsDto> _details;
|
|
private readonly Func<string, RendezVousDetailsDto?>? _getById;
|
|
|
|
public FakeRendezVousService(
|
|
IReadOnlyList<RendezVousSummaryDto>? items = null,
|
|
IReadOnlyList<RendezVousDetailsDto>? details = null,
|
|
Func<string, RendezVousDetailsDto?>? getById = null)
|
|
{
|
|
_items = items ?? Array.Empty<RendezVousSummaryDto>();
|
|
_details = details ?? Array.Empty<RendezVousDetailsDto>();
|
|
_getById = getById;
|
|
}
|
|
|
|
public Task<PagedResult<RendezVousSummaryDto>> SearchAsync(SearchRendezVousRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<RendezVousSummaryDto>(_items, 1, 1000, _items.Count, 1));
|
|
}
|
|
|
|
public Task<PagedResult<RendezVousDetailsDto>> SearchDetailsAsync(SearchRendezVousRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<RendezVousDetailsDto>(_details, 1, 1000, _details.Count, 1));
|
|
}
|
|
|
|
public Task<RendezVousDetailsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_getById is null ? null : _getById(id));
|
|
|
|
|
|
public Task<RendezVousDetailsDto> CreateAsync(CreateRendezVousRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<RendezVousDetailsDto> UpdateAsync(string id, UpdateRendezVousRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task CancelAsync(string id, AnnulationRendezVousDto annulation, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> Task.CompletedTask;
|
|
|
|
public Task ArchiveAsync(string id, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> Task.CompletedTask;
|
|
|
|
public Task<bool> SetTempsValideAsync(string rendezVousId, string collaborateurId, bool tempsValide, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> Task.FromResult(true);
|
|
}
|
|
|
|
private sealed class FakeMobileDocumentService : IMobileDocumentService
|
|
{
|
|
private readonly IReadOnlyList<DocumentSummaryDto> _documents;
|
|
|
|
public FakeMobileDocumentService(IReadOnlyList<DocumentSummaryDto>? documents = null)
|
|
=> _documents = documents ?? Array.Empty<DocumentSummaryDto>();
|
|
|
|
public Task<IReadOnlyList<DocumentSummaryDto>> GetByInterventionsAsync(
|
|
IReadOnlyList<long> numericInterventionIds,
|
|
IReadOnlyCollection<string>? allowedCategories,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult<IReadOnlyList<DocumentSummaryDto>>(_documents);
|
|
}
|
|
|
|
public Task<IReadOnlyList<DocumentSummaryDto>> GetByInterventionAsync(
|
|
string interventionId,
|
|
IReadOnlyCollection<string>? allowedCategories,
|
|
CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<DocumentSummaryDto>>(_documents);
|
|
|
|
public Task<DocumentDownloadResponse?> DownloadAsync(Guid documentGuid, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<long> ResolveDocumentPkAsync(Guid documentGuid, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<string?> ResolveDocumentFullPathAsync(Guid documentGuid, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<DocumentUploadResponse> UploadAsync(DocumentUploadRequest request, string categorieCle, AuditActor? auteur, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<IReadOnlyList<NoteInterneDto>> GetNotesAsync(Guid documentGuid, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<NoteInterneDto> AddNoteAsync(Guid documentGuid, string contenu, AuditActor? auteur, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
|
|
private sealed class FakeSuiviTempsService : ISuiviTempsService
|
|
{
|
|
private readonly IReadOnlyList<SuiviTempsDto> _items;
|
|
|
|
public FakeSuiviTempsService(IReadOnlyList<SuiviTempsDto>? items = null)
|
|
=> _items = items ?? Array.Empty<SuiviTempsDto>();
|
|
|
|
public Task<PagedResult<SuiviTempsDto>> SearchAsync(SuiviTempsSearchCriteria criteria, CancellationToken cancellationToken = default)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
return Task.FromResult(new PagedResult<SuiviTempsDto>(_items, 1, 1000, _items.Count, 1));
|
|
}
|
|
|
|
public Task<SuiviTempsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(_items.FirstOrDefault(i => i.Id == id));
|
|
|
|
public Task<SuiviTempsDto> CreateAsync(CreateSuiviTempsRequest request, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
}
|