using System.Security.Cryptography; using Microsoft.Extensions.Options; using Omika.Core.Compatibility; using Omika.Core.Configuration; using Omika.Core.Contracts.Common; using Omika.Core.Contracts.Mobile.Enrollment; using Omika.Core.Contracts.Mobile.License; using Omika.Core.Contracts.Mobile.Sync; using Omika.Core.Domain.Commands; using Omika.Core.Domain.Entities.Mobile; using Omika.Core.Domain.Entities.Organisation; using Omika.Core.Interfaces; using Omika.Core.Interfaces.Mobile; using Omika.Core.Services.Mobile; namespace Omika.Tests.Mobile; /// /// Tests du service de licence mobile (LicenseService). /// public class LicenseServiceTests { private const string CodeClient = "BATIK.01"; private const string NumeroLicence = "1ab053eb5e274e1f92455bbb7de9f2d3"; private const string DeviceId = "device-abc-123"; // Clé RSA éphémère partagée pour signer/vérifier les QR de test. Aucun secret // serveur : elle ne sert qu'à valider le format du QR produit par le service. private const string TestKeyId = "omika-qr-2026-prod-01"; private static readonly string TestPrivateKey; private static readonly string TestPublicKey; static LicenseServiceTests() { using var rsa = RSA.Create(2048); TestPrivateKey = rsa.ToXmlString(true); TestPublicKey = rsa.ToXmlString(false); } private static LicenseOptions DefaultOptions() => new() { OfflineGraceDays = 10 }; private static LicenseQrOptions QrOptions() => new() { KeyId = TestKeyId, PrivateKeyXml = TestPrivateKey }; private static Licence ValidLicence(string? deviceId = null, bool active = true, DateTime? expiration = null, string code = CodeClient) { return new Licence { Numero = NumeroLicence, CodeClient = code, ClientId = 1, DeviceId = deviceId, Active = active, DateExpiration = expiration, UserNom = "Dupont", UserPrenom = "Jean", UserId = 42, CollaborateurId = 7, Type = 5 }; } private static ClientBase ValidClient(string code = CodeClient) => new() { Id = 1, Code = code, Nom = "Société Test" }; private static LicenseService CreateService( FakeLicenceRepository repo, int offlineGraceDays = 10, FakeCollaborateurRepository? collaborateurRepository = null, LicenseQrOptions? qrOptions = null, string? supportEmail = "support@example.test") { var options = Options.Create(new LicenseOptions { OfflineGraceDays = offlineGraceDays }); var qr = Options.Create(qrOptions ?? new LicenseQrOptions()); var support = Options.Create(new SupportOptions { Email = supportEmail ?? string.Empty }); return new LicenseService( repo, collaborateurRepository ?? new FakeCollaborateurRepository(), new FakeIdentityResolver(), options, qr, support); } /// Décode et vérifie un QR produit par le service (registre kid → clé publique). private static EnrollmentQrPayload? DecodeQr(string? qrCode) => EnrollmentQrCodec.Decode(qrCode, new Dictionary { [TestKeyId] = TestPublicKey }); [Fact] public async Task Activate_Licence_Inexistante_Returns_Error() { // Arrange : client existant mais aucune licence. var repo = new FakeLicenceRepository { Client = ValidClient() }; var service = CreateService(repo); var request = new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }; // Act var result = await service.ActivateAsync(request); // Assert Assert.False(result.IsValid); Assert.Equal(LicenseErrorCodes.LicenceInconnue, result.ErrorCode); } [Fact] public async Task Activate_Licence_Inactive_Returns_Error() { // Arrange var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(active: false) }; var service = CreateService(repo); var request = new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }; // Act var result = await service.ActivateAsync(request); // Assert Assert.False(result.IsValid); Assert.Equal(LicenseErrorCodes.LicenceInactive, result.ErrorCode); } [Fact] public async Task Activate_Licence_Expiree_Returns_Error() { // Arrange var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(expiration: DateTime.Now.AddDays(-1)) }; var service = CreateService(repo); var request = new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }; // Act var result = await service.ActivateAsync(request); // Assert Assert.False(result.IsValid); Assert.Equal(LicenseErrorCodes.LicenceExpiree, result.ErrorCode); } [Fact] public async Task Activate_Licence_Attribuee_A_Un_Autre_Device_Returns_Error() { // Arrange var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(deviceId: "autre-device") }; var service = CreateService(repo); var request = new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }; // Act var result = await service.ActivateAsync(request); // Assert Assert.False(result.IsValid); Assert.Equal(LicenseErrorCodes.LicenceAttribuee, result.ErrorCode); } [Fact] public async Task Activate_Valide_Returns_Success_And_Assigns_Device() { // Arrange var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = "https://api-a.example.test" }; var service = CreateService(repo); var request = new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId, OsType = "Android", OsVersion = "15", AppVersion = "1.0.0" }; // Act var result = await service.ActivateAsync(request); // Assert Assert.True(result.IsValid); Assert.Equal(LicenseErrorCodes.Ok, result.ErrorCode); Assert.Equal(CodeClient, result.CodeClient); Assert.Equal(NumeroLicence, result.NumeroLicence); Assert.Equal("Dupont", result.UserNom); Assert.Equal("Jean", result.UserPrenom); Assert.Equal("42", result.UserId); Assert.Equal("7", result.CollaborateurId); Assert.True(repo.DeviceAssigned); Assert.True(repo.HistoAdded); } [Fact] public async Task Offline_Grace_Days_Comes_From_Configuration() { // Arrange var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = "https://api-a.example.test" }; var service = CreateService(repo, offlineGraceDays: 25); var request = new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }; // Act var result = await service.ActivateAsync(request); // Assert : la durée offline provient de la configuration, pas codée en dur. Assert.True(result.IsValid); Assert.Equal(25, result.OfflineGraceDays); } [Fact] public async Task Activate_Valide_RetourneEmailSupportConfig() { // Arrange var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = "https://api-a.example.test" }; var service = CreateService(repo, supportEmail: "support@example.test"); // Act var result = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }); // Assert : l'email de support provient de la configuration API. Assert.True(result.IsValid); Assert.Equal("support@example.test", result.SupportEmail); } [Fact] public async Task Activate_SupportEmailVide_RetourneNull() { // Arrange : email de support vide/espaces dans la configuration. var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = "https://api-a.example.test" }; var service = CreateService(repo, supportEmail: " "); // Act var result = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }); // Assert : jamais une chaîne vide transmise au mobile (masquage propre). Assert.True(result.IsValid); Assert.Null(result.SupportEmail); } [Fact] public async Task Validate_DeviceId_Incompatible_Returns_Error() { // Arrange var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(deviceId: "autre-device") }; var service = CreateService(repo); var request = new LicenseValidateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }; // Act var result = await service.ValidateAsync(request); // Assert Assert.False(result.IsValid); Assert.Equal(LicenseErrorCodes.LicenceAttribuee, result.ErrorCode); } // ===== Résolution multi-client de l'URL API depuis la base Licence ===== [Fact] public async Task Activate_ApiUrlHttpsValide_Retournee() { var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = "https://api-a.example.test" }; var service = CreateService(repo); var result = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }); Assert.True(result.IsValid); Assert.Equal("https://api-a.example.test", result.ApiUrl); } [Fact] public async Task Activate_ApiUrlAbsente_Refus() { var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = null }; var service = CreateService(repo); var result = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }); Assert.False(result.IsValid); Assert.Equal(LicenseErrorCodes.ClientConfig, result.ErrorCode); } [Fact] public async Task Activate_ApiUrlVide_Refus() { var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = " " }; var service = CreateService(repo); var result = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }); Assert.False(result.IsValid); Assert.Equal(LicenseErrorCodes.ClientConfig, result.ErrorCode); } [Fact] public async Task Activate_ApiUrlHttp_Refus() { var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = "http://api-a.example.test" }; var service = CreateService(repo); var result = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }); Assert.False(result.IsValid); Assert.Equal(LicenseErrorCodes.ClientConfig, result.ErrorCode); } [Fact] public async Task Activate_ApiUrlLocalhost_Refus() { var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = "https://localhost/api" }; var service = CreateService(repo); var result = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }); Assert.False(result.IsValid); Assert.Equal(LicenseErrorCodes.ClientConfig, result.ErrorCode); } [Fact] public async Task Activate_ResoutDepuisBaseLicence_SansParametreSystemeMetier() { // L'URL est résolue depuis la base Licence (fake), pas depuis la base métier. // Le fake collaborateur (base métier) ne doit être sollicité pour AUCUN // paramètre système pendant l'activation : SRV_API_MOBILE n'est plus lu. var collaborateurRepository = new FakeCollaborateurRepository(); var repo = new FakeLicenceRepository { Client = ValidClient(), Licence = ValidLicence(), MobileApiUrl = "https://api-a.example.test" }; var service = CreateService(repo, collaborateurRepository: collaborateurRepository); var result = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = CodeClient, NumeroLicence = NumeroLicence, DeviceId = DeviceId }); Assert.True(result.IsValid); Assert.Equal("https://api-a.example.test", result.ApiUrl); Assert.Empty(collaborateurRepository.RequestedParametreCodes); } [Fact] public async Task MultiClient_MemeService_RetourneUrlsDistinctes() { // Une seule instance de service de licences résout des URLs distinctes // pour deux clients, sans changer de configuration/base/build/instance. var repo = new FakeLicenceRepository(); repo.ClientsByCode["CLIENT.A"] = ValidClient("CLIENT.A"); repo.ClientsByCode["CLIENT.B"] = ValidClient("CLIENT.B"); repo.LicencesByCode["CLIENT.A"] = ValidLicence(code: "CLIENT.A"); repo.LicencesByCode["CLIENT.B"] = ValidLicence(code: "CLIENT.B"); repo.MobileApiUrlsByCode["CLIENT.A"] = "https://api-a.example.test"; repo.MobileApiUrlsByCode["CLIENT.B"] = "https://api-b.example.test"; var service = CreateService(repo); var resultA = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = "CLIENT.A", NumeroLicence = NumeroLicence, DeviceId = "device-a" }); var resultB = await service.ActivateAsync(new LicenseActivateRequest { CodeClient = "CLIENT.B", NumeroLicence = NumeroLicence, DeviceId = "device-b" }); Assert.True(resultA.IsValid); Assert.True(resultB.IsValid); Assert.Equal("https://api-a.example.test", resultA.ApiUrl); Assert.Equal("https://api-b.example.test", resultB.ApiUrl); } // ===== Workflow administratif : attribution / consultation / libération ===== private static Licence FreeLicence(string numero) => new() { Numero = numero, ClientId = 1, CodeClient = CodeClient, Active = true, Type = 5 }; private static FakeCollaborateurRepository LiaisonRepo() => new() { Liaison = new CollaborateurUtilisateur(7, 42, "Dupont", "Jean"), ParametresSysteme = new Dictionary { ["SRV_LICENCE_MOBILE"] = "https://licence.example.test" } }; [Fact] public async Task Assign_LicenceLibre_AttribueEtRetourneQrSigne() { var repo = new FakeLicenceRepository { Client = ValidClient() }; repo.FreeLicences.Add(FreeLicence("LIC-FREE-1")); var service = CreateService(repo, collaborateurRepository: LiaisonRepo(), qrOptions: QrOptions()); CollaborateurLicenceDto? dto = await service.AssignMobileLicenceAsync("7", CodeClient); Assert.NotNull(dto); Assert.Equal("LIC-FREE-1", dto.Numero); Assert.Equal(CodeClient, dto.CodeClient); Assert.True(dto.Active); Assert.False(string.IsNullOrWhiteSpace(dto.QrCode)); Assert.Equal(1, repo.AssignCallCount); EnrollmentQrPayload? payload = DecodeQr(dto.QrCode); Assert.NotNull(payload); Assert.Equal(1, payload.Version); Assert.Equal(TestKeyId, payload.KeyId); Assert.Equal(CodeClient, payload.CodeClient); Assert.Equal("LIC-FREE-1", payload.NumeroLicence); Assert.Equal("https://licence.example.test", payload.UrlLicence); } [Fact] public async Task Assign_SecondeAttribution_MemeLicence_SansConsommer() { var repo = new FakeLicenceRepository { Client = ValidClient() }; repo.FreeLicences.Add(FreeLicence("LIC-FREE-1")); var service = CreateService(repo, collaborateurRepository: LiaisonRepo(), qrOptions: QrOptions()); CollaborateurLicenceDto? first = await service.AssignMobileLicenceAsync("7", CodeClient); CollaborateurLicenceDto? second = await service.AssignMobileLicenceAsync("7", CodeClient); Assert.NotNull(first); Assert.NotNull(second); Assert.Equal(first.Numero, second.Numero); Assert.Equal(first.QrCode, second.QrCode); // Idempotence : une seule écriture, aucune nouvelle licence consommée. Assert.Equal(1, repo.AssignCallCount); Assert.Single(repo.AssignedNumeros); } [Fact] public async Task Assign_OmikaGuid_ResoutPkEtIdempotent() { // Omika : l'identité publique est un GUID physique ; le resolver ne // fournit pas de PK numérique (ResolvePhysicalIdentity → Guid). Le CO_PKID // est résolu via la fiche collaborateur. Sans cette résolution, l'idempotence // est sautée et une seconde attribution crée un doublon de licence. Guid guid = Guid.NewGuid(); var collaborateur = new Collaborateur(); collaborateur.SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 7); var collaborateurRepo = new FakeCollaborateurRepository { Liaison = new CollaborateurUtilisateur(7, 42, "Dupont", "Jean"), Details = collaborateur }; var repo = new FakeLicenceRepository { Client = ValidClient() }; repo.FreeLicences.Add(FreeLicence("LIC-FREE-1")); var service = CreateService(repo, collaborateurRepository: collaborateurRepo, qrOptions: QrOptions()); CollaborateurLicenceDto? first = await service.AssignMobileLicenceAsync(guid.ToString("D"), CodeClient); CollaborateurLicenceDto? second = await service.AssignMobileLicenceAsync(guid.ToString("D"), CodeClient); Assert.NotNull(first); Assert.NotNull(second); Assert.Equal(first.Numero, second.Numero); Assert.Equal(1, repo.AssignCallCount); } [Fact] public async Task Get_OmikaGuid_ResoutPkEtRetourneLicence() { // Omika : la lecture de la licence doit résoudre le GUID physique en PK // numérique via la fiche collaborateur, sinon la fiche affiche « aucune // licence » alors qu'une licence est bien attribuée. Guid guid = Guid.NewGuid(); var collaborateur = new Collaborateur(); collaborateur.SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: 7); var collaborateurRepo = new FakeCollaborateurRepository { Details = collaborateur }; var repo = new FakeLicenceRepository { Client = ValidClient() }; repo.LicenceByCollaborateur[7] = FreeLicence("LIC-FREE-1"); var service = CreateService(repo, collaborateurRepository: collaborateurRepo, qrOptions: QrOptions()); CollaborateurLicenceDto? dto = await service.GetMobileLicenceForCollaborateurAsync(guid.ToString("D"), CodeClient); Assert.NotNull(dto); Assert.Equal("LIC-FREE-1", dto.Numero); } [Fact] public async Task Assign_AucuneLicenceLibre_LanceInvalidOperation() { var repo = new FakeLicenceRepository { Client = ValidClient() }; var service = CreateService(repo, collaborateurRepository: LiaisonRepo(), qrOptions: QrOptions()); await Assert.ThrowsAsync( () => service.AssignMobileLicenceAsync("7", CodeClient)); } [Fact] public async Task Assign_CollaborateurSansCompteUtilisateur_LanceInvalidOperation() { var repo = new FakeLicenceRepository { Client = ValidClient() }; repo.FreeLicences.Add(FreeLicence("LIC-FREE-1")); // Aucune liaison collaborateur → compte utilisateur (défaut : null). var service = CreateService(repo, collaborateurRepository: new FakeCollaborateurRepository(), qrOptions: QrOptions()); await Assert.ThrowsAsync( () => service.AssignMobileLicenceAsync("7", CodeClient)); } [Fact] public async Task Get_SansLicence_RetourneNull() { var repo = new FakeLicenceRepository { Client = ValidClient() }; var service = CreateService(repo, collaborateurRepository: LiaisonRepo(), qrOptions: QrOptions()); CollaborateurLicenceDto? dto = await service.GetMobileLicenceForCollaborateurAsync("7", CodeClient); Assert.Null(dto); } [Fact] public async Task Release_LicencePresente_Libere() { var repo = new FakeLicenceRepository { Client = ValidClient() }; repo.LicenceByCollaborateur[7] = FreeLicence("LIC-FREE-1"); var service = CreateService(repo, collaborateurRepository: LiaisonRepo(), qrOptions: QrOptions()); bool released = await service.ReleaseMobileLicenceAsync("7"); Assert.True(released); Assert.Equal(1, repo.ReleaseCallCount); Assert.Empty(repo.LicenceByCollaborateur); } [Fact] public async Task Release_SansLicence_RetourneFalse() { var repo = new FakeLicenceRepository { Client = ValidClient() }; var service = CreateService(repo, collaborateurRepository: LiaisonRepo(), qrOptions: QrOptions()); bool released = await service.ReleaseMobileLicenceAsync("7"); Assert.False(released); Assert.Equal(0, repo.ReleaseCallCount); } [Fact] public async Task Assign_ApresLiberation_AttribueNouvelleLicence() { var repo = new FakeLicenceRepository { Client = ValidClient() }; repo.FreeLicences.Add(FreeLicence("LIC-FREE-1")); var service = CreateService(repo, collaborateurRepository: LiaisonRepo(), qrOptions: QrOptions()); CollaborateurLicenceDto? first = await service.AssignMobileLicenceAsync("7", CodeClient); Assert.Equal("LIC-FREE-1", first?.Numero); await service.ReleaseMobileLicenceAsync("7"); // Nouvelle licence libre disponible après libération. repo.FreeLicences.Add(FreeLicence("LIC-FREE-2")); CollaborateurLicenceDto? second = await service.AssignMobileLicenceAsync("7", CodeClient); Assert.Equal("LIC-FREE-2", second?.Numero); Assert.Equal(2, repo.AssignCallCount); } // ===== Fake ===== private sealed class FakeIdentityResolver : IMobileEntityIdentityResolver { public Guid CreateCanonicalIdentity(string entityType, PhysicalEntityIdentity physical) => physical.Guid ?? Guid.Empty; public PhysicalEntityIdentity? ResolvePhysicalIdentity(string entityType, Guid publicGuid) => null; public bool IsCanonical(string entityType, Guid publicGuid) => publicGuid != Guid.Empty; } private sealed class FakeLicenceRepository : ILicenceRepository { public ClientBase? Client { get; set; } public Licence? Licence { get; set; } public bool DeviceAssigned { get; private set; } public bool HistoAdded { get; private set; } /// URL d'API mobile retournée par défaut (hors dictionnaire). public string? MobileApiUrl { get; set; } /// Variante multi-client : résolution par code client. public Dictionary ClientsByCode { get; } = new(); public Dictionary LicencesByCode { get; } = new(); public Dictionary MobileApiUrlsByCode { get; } = new(); // --- État du workflow administratif de licence mobile --- public List FreeLicences { get; } = new(); public Dictionary LicenceByCollaborateur { get; } = new(); public int AssignCallCount { get; private set; } public int ReleaseCallCount { get; private set; } public List AssignedNumeros { get; } = new(); public Task GetClientByCodeAsync(string codeClient, CancellationToken cancellationToken = default) => Task.FromResult(ClientsByCode.TryGetValue(codeClient, out ClientBase? c) ? c : Client); public Task GetByNumeroAsync(string numero, CancellationToken cancellationToken = default) => Task.FromResult(Licence); public Task GetByNumeroAndClientAsync(string numero, string codeClient, CancellationToken cancellationToken = default) { Licence? assigned = LicenceByCollaborateur.Values.FirstOrDefault(l => l.Numero == numero); return Task.FromResult(assigned ?? (LicencesByCode.TryGetValue(codeClient, out Licence? l) ? l : Licence)); } public Task GetMobileApiUrlForClientAsync(string codeClient, CancellationToken cancellationToken = default) => Task.FromResult(MobileApiUrlsByCode.TryGetValue(codeClient, out string? url) ? url : MobileApiUrl); public Task GetByCollaborateurAsync(long collaborateurId, CancellationToken cancellationToken = default) => Task.FromResult(LicenceByCollaborateur.TryGetValue(collaborateurId, out Licence? licence) ? licence : null); public Task GetFreeMobileLicenceAsync(long clientId, CancellationToken cancellationToken = default) => Task.FromResult(FreeLicences.Count > 0 ? FreeLicences[0] : null); public Task AssignMobileLicenceAsync(string numero, long userId, long collaborateurId, string? nom, string? prenom, CancellationToken cancellationToken = default) { AssignCallCount++; AssignedNumeros.Add(numero); FreeLicences.RemoveAll(l => l.Numero == numero); LicenceByCollaborateur[collaborateurId] = new Licence { Numero = numero, ClientId = Client?.Id ?? 0, CodeClient = Client?.Code, CollaborateurId = collaborateurId, UserId = userId, UserNom = nom, UserPrenom = prenom, Active = true, Type = 5 }; return Task.FromResult(true); } public Task ReleaseMobileLicenceAsync(string numero, CancellationToken cancellationToken = default) { ReleaseCallCount++; var pair = LicenceByCollaborateur.FirstOrDefault(kv => kv.Value.Numero == numero); if (pair.Value is not null) { LicenceByCollaborateur.Remove(pair.Key); } return Task.FromResult(true); } public Task AssignDeviceAsync(string numero, string deviceId, CancellationToken cancellationToken = default) { DeviceAssigned = true; return Task.FromResult(true); } public Task AddHistoAsync(LicenceHistoEntry entry, CancellationToken cancellationToken = default) { HistoAdded = true; return Task.CompletedTask; } } private sealed class FakeCollaborateurRepository : ICollaborateurRepository { public List RequestedParametreCodes { get; } = new(); public CollaborateurUtilisateur? Liaison { get; set; } public Collaborateur? Details { get; set; } public Dictionary ParametresSysteme { get; set; } = new(); public Task> SearchAsync(CollaborateurSearchCriteria criteria, CancellationToken cancellationToken = default) => Task.FromResult(new PagedResult([], 1, 20, 0, 0)); public Task> SearchDetailsAsync(CollaborateurSearchCriteria criteria, CancellationToken cancellationToken = default) => Task.FromResult(new PagedResult([], 1, 20, 0, 0)); public Task GetDetailsAsync(string id, CancellationToken cancellationToken = default) => Task.FromResult(Details); public Task GetUtilisateurByCollaborateurAsync(string collaborateurId, CancellationToken cancellationToken = default) => Task.FromResult(Liaison); public Task GetParametreSystemeValueAsync(string code, CancellationToken cancellationToken = default) { RequestedParametreCodes.Add(code); return Task.FromResult(ParametresSysteme.TryGetValue(code, out string? value) ? value : null); } public Task CreateAsync(CollaborateurCreateCommand command, CancellationToken cancellationToken = default) => throw new NotSupportedException(); public Task UpdateAsync(string id, CollaborateurUpdateCommand command, CancellationToken cancellationToken = default) => Task.FromResult(null); public Task ArchiveAsync(string id, CancellationToken cancellationToken = default) => Task.FromResult(true); public Task> GetSocieteIdsAsync(long collaborateurId, CancellationToken cancellationToken = default) => Task.FromResult>([]); public Task> GetAllowedCollaborateurIdsAsync(long collaborateurId, bool includeSocietePrincipale, bool includeSocietes, bool includeEquipe, CancellationToken cancellationToken = default) => Task.FromResult>([]); } }