Files
2026-10-23 18:46:50 +02:00

834 lines
31 KiB
C#

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;
/// <summary>
/// Tests du service de licence mobile (LicenseService).
/// </summary>
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);
}
/// <summary>Décode et vérifie un QR produit par le service (registre kid → clé publique).</summary>
private static EnrollmentQrPayload? DecodeQr(string? qrCode)
=> EnrollmentQrCodec.Decode(qrCode, new Dictionary<string, string> { [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<string, string?>
{
["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<InvalidOperationException>(
() => 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<InvalidOperationException>(
() => 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; }
/// <summary>URL d'API mobile retournée par défaut (hors dictionnaire).</summary>
public string? MobileApiUrl { get; set; }
/// <summary>Variante multi-client : résolution par code client.</summary>
public Dictionary<string, ClientBase> ClientsByCode { get; } = new();
public Dictionary<string, Licence> LicencesByCode { get; } = new();
public Dictionary<string, string?> MobileApiUrlsByCode { get; } = new();
// --- État du workflow administratif de licence mobile ---
public List<Licence> FreeLicences { get; } = new();
public Dictionary<long, Licence> LicenceByCollaborateur { get; } = new();
public int AssignCallCount { get; private set; }
public int ReleaseCallCount { get; private set; }
public List<string> AssignedNumeros { get; } = new();
public Task<ClientBase?> GetClientByCodeAsync(string codeClient, CancellationToken cancellationToken = default)
=> Task.FromResult(ClientsByCode.TryGetValue(codeClient, out ClientBase? c) ? c : Client);
public Task<Licence?> GetByNumeroAsync(string numero, CancellationToken cancellationToken = default)
=> Task.FromResult(Licence);
public Task<Licence?> 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<string?> GetMobileApiUrlForClientAsync(string codeClient, CancellationToken cancellationToken = default)
=> Task.FromResult(MobileApiUrlsByCode.TryGetValue(codeClient, out string? url) ? url : MobileApiUrl);
public Task<Licence?> GetByCollaborateurAsync(long collaborateurId, CancellationToken cancellationToken = default)
=> Task.FromResult(LicenceByCollaborateur.TryGetValue(collaborateurId, out Licence? licence) ? licence : null);
public Task<Licence?> GetFreeMobileLicenceAsync(long clientId, CancellationToken cancellationToken = default)
=> Task.FromResult(FreeLicences.Count > 0 ? FreeLicences[0] : null);
public Task<bool> 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<bool> 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<bool> 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<string> RequestedParametreCodes { get; } = new();
public CollaborateurUtilisateur? Liaison { get; set; }
public Collaborateur? Details { get; set; }
public Dictionary<string, string?> ParametresSysteme { get; set; } = new();
public Task<PagedResult<Collaborateur>> SearchAsync(CollaborateurSearchCriteria criteria, CancellationToken cancellationToken = default)
=> Task.FromResult(new PagedResult<Collaborateur>([], 1, 20, 0, 0));
public Task<PagedResult<Collaborateur>> SearchDetailsAsync(CollaborateurSearchCriteria criteria, CancellationToken cancellationToken = default)
=> Task.FromResult(new PagedResult<Collaborateur>([], 1, 20, 0, 0));
public Task<Collaborateur?> GetDetailsAsync(string id, CancellationToken cancellationToken = default)
=> Task.FromResult(Details);
public Task<CollaborateurUtilisateur?> GetUtilisateurByCollaborateurAsync(string collaborateurId, CancellationToken cancellationToken = default)
=> Task.FromResult(Liaison);
public Task<string?> GetParametreSystemeValueAsync(string code, CancellationToken cancellationToken = default)
{
RequestedParametreCodes.Add(code);
return Task.FromResult(ParametresSysteme.TryGetValue(code, out string? value) ? value : null);
}
public Task<Collaborateur> CreateAsync(CollaborateurCreateCommand command, CancellationToken cancellationToken = default)
=> throw new NotSupportedException();
public Task<Collaborateur?> UpdateAsync(string id, CollaborateurUpdateCommand command, CancellationToken cancellationToken = default)
=> Task.FromResult<Collaborateur?>(null);
public Task<bool> ArchiveAsync(string id, CancellationToken cancellationToken = default)
=> Task.FromResult(true);
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>>([]);
}
}