using System.Net.Http; using System.Security.Cryptography; using Omika.App.Local.Abstractions; using Omika.App.Local.Models; using Omika.App.Local.Repositories; using Omika.App.Local.Services; using Omika.Core.Contracts.Mobile.License; using Omika.Core.Observability.Diagnostic; using Omika.Core.Services.Mobile; namespace Omika.Tests.Mobile.Local; /// /// Tests du service d'inscription (QR → licence → API → stockage). /// public class MobileEnrollmentServiceTests : LocalTestBase { private const string Kid = "test-kid-1"; private sealed class FakeLicenseEnrollmentClient : ILicenseEnrollmentClient { public MobileLicenseSessionResponse ActivateResponse { get; set; } = new(); public string? LastUrl { get; private set; } public LicenseActivateRequest? LastRequest { get; private set; } public Task ActivateAsync( string licenseServiceUrl, LicenseActivateRequest request, CancellationToken cancellationToken = default) { LastUrl = licenseServiceUrl; LastRequest = request; return Task.FromResult(ActivateResponse); } public Task ValidateAsync( string licenseServiceUrl, LicenseValidateRequest request, CancellationToken cancellationToken = default) => Task.FromResult(new MobileLicenseSessionResponse()); } private sealed class FakeDeviceIdProvider : IDeviceIdProvider { public Task GetDeviceIdAsync(CancellationToken cancellationToken = default) => Task.FromResult("device-1"); } private static string CreateSignedQr(string code, string numero, string url, out IReadOnlyDictionary registry) { using var rsa = RSA.Create(2048); registry = new Dictionary { [Kid] = rsa.ToXmlString(false) }; return EnrollmentQrCodec.Sign( EnrollmentQrCodec.BuildPayloadJson(Kid, code, numero, url), rsa.ToXmlString(true)); } private MobileEnrollmentService CreateService( FakeLicenseEnrollmentClient licenseClient, IReadOnlyDictionary registry, MobileApiClient apiClient, MobileEnrollmentStore? store = null) => new( licenseClient, store ?? new MobileEnrollmentStore(Database), new FakeDeviceIdProvider(), new StubSessionService(), apiClient, registry); [Fact] public async Task LicenceInvalide_OmikaApiUrlNonEnregistree() { var licenseClient = new FakeLicenseEnrollmentClient { ActivateResponse = new MobileLicenseSessionResponse { License = new LicenseResponse { IsValid = false, ErrorCode = "E3002", ErrorMessage = "Licence inactive" } } }; var apiClient = new MobileApiClient(new HttpClient()); string qr = CreateSignedQr("BATIK.01", "LIC-001", "https://licence.example.com", out var registry); var service = CreateService(licenseClient, registry, apiClient); EnrollmentResult result = await service.EnrollAsync(qr); Assert.False(result.Success); Assert.Null(await service.LoadAsync()); Assert.Null(apiClient.BaseUrl); } [Fact] public async Task LicenceValideSansApiUrl_InscriptionRefusee() { var licenseClient = new FakeLicenseEnrollmentClient { ActivateResponse = new MobileLicenseSessionResponse { License = new LicenseResponse { IsValid = true, ApiUrl = null } } }; var apiClient = new MobileApiClient(new HttpClient()); string qr = CreateSignedQr("BATIK.01", "LIC-001", "https://licence.example.com", out var registry); var service = CreateService(licenseClient, registry, apiClient); EnrollmentResult result = await service.EnrollAsync(qr); Assert.False(result.Success); Assert.Null(await service.LoadAsync()); } [Fact] public async Task LicenceValideApiUrlHttp_Refusee() { var licenseClient = new FakeLicenseEnrollmentClient { ActivateResponse = new MobileLicenseSessionResponse { License = new LicenseResponse { IsValid = true, ApiUrl = "http://api.example.com" } } }; var apiClient = new MobileApiClient(new HttpClient()); string qr = CreateSignedQr("BATIK.01", "LIC-001", "https://licence.example.com", out var registry); var service = CreateService(licenseClient, registry, apiClient); EnrollmentResult result = await service.EnrollAsync(qr); Assert.False(result.Success); Assert.Null(await service.LoadAsync()); } [Fact] public async Task LicenceValideApiUrlHttps_Enregistree() { var licenseClient = new FakeLicenseEnrollmentClient { ActivateResponse = new MobileLicenseSessionResponse { License = new LicenseResponse { IsValid = true, ApiUrl = "https://api.example.com" }, AccessToken = "token-1" } }; var apiClient = new MobileApiClient(new HttpClient()); string qr = CreateSignedQr("BATIK.01", "LIC-001", "https://licence.example.com", out var registry); var service = CreateService(licenseClient, registry, apiClient); EnrollmentResult result = await service.EnrollAsync(qr); Assert.True(result.Success); Assert.Equal("https://api.example.com", result.Configuration!.OmikaApiUrl); Assert.Equal("https://licence.example.com", result.Configuration.LicenseServiceUrl); Assert.Equal("https://api.example.com", apiClient.BaseUrl); MobileEnrollmentConfiguration? persisted = await service.LoadAsync(); Assert.NotNull(persisted); Assert.Equal("https://api.example.com", persisted!.OmikaApiUrl); } [Fact] public async Task QrInvalide_RefuseSansAppelReseau() { var licenseClient = new FakeLicenseEnrollmentClient(); var apiClient = new MobileApiClient(new HttpClient()); string qr = CreateSignedQr("BATIK.01", "LIC-001", "https://licence.example.com", out var registry); var service = CreateService(licenseClient, registry, apiClient); EnrollmentResult result = await service.EnrollAsync("qr-corrompu"); Assert.False(result.Success); Assert.Null(licenseClient.LastUrl); // aucun appel au service de licence } [Fact] public async Task Enroll_AvecDeviceInfo_RemplitChampsActivation() { var licenseClient = new FakeLicenseEnrollmentClient { ActivateResponse = new MobileLicenseSessionResponse { License = new LicenseResponse { IsValid = false } // suffit pour observer la requête } }; var apiClient = new MobileApiClient(new HttpClient()); string qr = CreateSignedQr("BATIK.01", "LIC-001", "https://licence.example.com", out var registry); var deviceInfo = new DeviceInfoSnapshot { OperatingSystem = "Android", OperatingSystemVersion = "16", AppVersion = "1.0.0", DeviceManufacturer = "Xiaomi", DeviceModel = "23113RKC6G" }; var service = new MobileEnrollmentService( licenseClient, new MobileEnrollmentStore(Database), new FakeDeviceIdProvider(), new StubSessionService(), apiClient, registry, deviceInfo); await service.EnrollAsync(qr); Assert.Equal("Android", licenseClient.LastRequest!.OsType); Assert.Equal("16", licenseClient.LastRequest.OsVersion); Assert.Equal("1.0.0", licenseClient.LastRequest.AppVersion); Assert.Equal("manufacturer=Xiaomi;model=23113RKC6G", licenseClient.LastRequest.DeviceInfo); Assert.Null(licenseClient.LastRequest.NotificationKey); } }