570 lines
22 KiB
C#
570 lines
22 KiB
C#
using System.Net.Http;
|
|
using Omika.App.Local.Abstractions;
|
|
using Omika.App.Local.Models;
|
|
using Omika.App.Navigation;
|
|
using Omika.App.Services;
|
|
using Omika.App.ViewModels;
|
|
using Omika.Core.Contracts.Common;
|
|
using Omika.Core.Contracts.Mobile.Interventions;
|
|
using Omika.Core.Contracts.Mobile.Sync;
|
|
using Omika.Core.Observability;
|
|
|
|
namespace Omika.Tests.Mobile.Local;
|
|
|
|
/// <summary>
|
|
/// Tests du ViewModel Interventions (modèle offline-first réel) : recherche à
|
|
/// la demande, dernière recherche, favoris, onglets Récentes/Épinglées, lookup
|
|
/// humain Client/Site/Collaborateur.
|
|
///
|
|
/// Couvre aussi la politique d'erreur de la recherche : un échec réseau — ticket
|
|
/// de production du 14/10, « Connection failure » — est un état hors ligne
|
|
/// journalisé en Warning/P2, jamais un incident Error/P1.
|
|
/// </summary>
|
|
[Collection("TelemetrieStatique")]
|
|
public class InterventionsViewModelTests
|
|
{
|
|
private static InterventionsViewModel CreateVm(IInterventionLocalService service)
|
|
=> new InterventionsViewModel(
|
|
service,
|
|
new FakeParametreMetierService(),
|
|
new NavigationService(),
|
|
new PermissionService(),
|
|
Omika.Core.Observability.NullTelemetryService.Instance);
|
|
|
|
[Fact]
|
|
public async Task Load_SansRecherche_ListeVide()
|
|
{
|
|
var service = new FakeInterventionService();
|
|
var vm = CreateVm(service);
|
|
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
Assert.False(vm.HasItems);
|
|
Assert.True(vm.IsEmptyRecent);
|
|
Assert.Equal("0 résultat", vm.ResultCountLabel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Search_Reussi_RemplaceDerniereRecherche()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
LastSearch = [Card("INT-1", "Intervention A")]
|
|
};
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
Assert.Single(vm.Items);
|
|
|
|
service.SearchResults = [Card("INT-2", "Intervention B"), Card("INT-3", "Intervention C")];
|
|
vm.ReferenceSearch = "INT";
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal(2, vm.Items.Count);
|
|
Assert.False(vm.IsShowingPinned);
|
|
Assert.Equal("2 résultats", vm.ResultCountLabel);
|
|
Assert.Equal("INT", service.LastRequest?.Reference);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Search_Echoue_ConservePrecedente()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
LastSearch = [Card("INT-1", "Intervention A")]
|
|
};
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
Assert.Single(vm.Items);
|
|
|
|
// Erreur de recherche NEUTRE : ce test porte sur la conservation de la liste
|
|
// précédente. La politique « indisponibilité réseau → message français stable »
|
|
// est couverte par les tests d'agenda (Load_IOExceptionReelle_*).
|
|
service.SearchException = new InvalidOperationException("échec de recherche");
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.Single(vm.Items);
|
|
Assert.Equal("échec de recherche", vm.ErrorMessage);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ticket de production du 14/10 (recherche d'interventions, « Connection failure ») :
|
|
/// un échec réseau n'est pas un incident. Il est journalisé en Warning/P2, signalé
|
|
/// par le bandeau hors ligne (aucun message d'erreur), et la liste précédente —
|
|
/// dernière recherche réussie — reste affichée.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Search_EchecReseau_SignaleHorsLigneSansIncident()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
LastSearch = [Card("INT-1", "Intervention A")]
|
|
};
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
var telemetry = new CapturingTelemetry();
|
|
ExceptionTelemetry.Initialize(telemetry, null);
|
|
try
|
|
{
|
|
service.SearchException = new HttpRequestException("Connection failure");
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
}
|
|
finally
|
|
{
|
|
ExceptionTelemetry.Initialize(null, null);
|
|
}
|
|
|
|
Assert.True(vm.IsOffline);
|
|
Assert.Null(vm.ErrorMessage);
|
|
Assert.Single(vm.Items);
|
|
|
|
AssertAucunIncident(telemetry);
|
|
DiagnosticEvent evt = EvenementInterventions(telemetry);
|
|
Assert.Equal(TelemetryLevel.Warning, evt.Level);
|
|
Assert.Equal("HttpRequestException", evt.ErrorCode);
|
|
Assert.StartsWith("Connection failure", evt.Message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aucune URL d'API configurée (appareil non inscrit) : ce n'est pas une coupure
|
|
/// réseau — l'appareil peut être en ligne. On affiche l'action à faire
|
|
/// (« rescannez le QR ») et jamais le bandeau hors ligne, sans incident P1.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Search_SansUrlDApi_MessageActionnableSansBandeauHorsLigne()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
SearchException = new MobileEnrollmentRequiredException()
|
|
};
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
var telemetry = new CapturingTelemetry();
|
|
ExceptionTelemetry.Initialize(telemetry, null);
|
|
try
|
|
{
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
}
|
|
finally
|
|
{
|
|
ExceptionTelemetry.Initialize(null, null);
|
|
}
|
|
|
|
Assert.False(vm.IsOffline);
|
|
Assert.Equal(
|
|
"Inscription incomplète — rescannez le QR code pour reconnecter l'application.",
|
|
vm.ErrorMessage);
|
|
|
|
AssertAucunIncident(telemetry);
|
|
DiagnosticEvent evt = EvenementInterventions(telemetry);
|
|
Assert.Equal(TelemetryLevel.Warning, evt.Level);
|
|
Assert.Equal("MobileEnrollmentRequiredException", evt.ErrorCode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hors ligne SANS résultat précédent : l'écran ne doit pas annoncer « aucune
|
|
/// recherche récente » — l'absence n'a pas été constatée par le serveur, c'est
|
|
/// le bandeau hors ligne qui porte l'information.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Search_EchecReseau_SansResultatPrecedent_MasqueLEtatVide()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
SearchException = new HttpRequestException("Connection failure")
|
|
};
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.True(vm.IsOffline);
|
|
Assert.False(vm.IsEmptyRecent);
|
|
Assert.Empty(vm.Items);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contre-épreuve : une erreur métier (hors réseau) reste un incident Error/P1
|
|
/// avec son message, et n'active pas le bandeau hors ligne.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Search_ErreurMetier_ResteUnIncident()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
SearchException = new InvalidOperationException("échec de recherche")
|
|
};
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
var telemetry = new CapturingTelemetry();
|
|
ExceptionTelemetry.Initialize(telemetry, null);
|
|
try
|
|
{
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
}
|
|
finally
|
|
{
|
|
ExceptionTelemetry.Initialize(null, null);
|
|
}
|
|
|
|
Assert.False(vm.IsOffline);
|
|
Assert.Equal("échec de recherche", vm.ErrorMessage);
|
|
|
|
DiagnosticEvent evt = EvenementInterventions(telemetry);
|
|
Assert.Equal(TelemetryLevel.Error, evt.Level);
|
|
Assert.Equal("InvalidOperationException", evt.ErrorCode);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Une recherche réussie après un échec réseau efface l'état hors ligne : le
|
|
/// bandeau ne doit pas rester affiché une fois le serveur revenu.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Search_ReussiApresEchecReseau_EffaceLEtatHorsLigne()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
SearchException = new HttpRequestException("Connection failure")
|
|
};
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
Assert.True(vm.IsOffline);
|
|
|
|
service.SearchException = null;
|
|
service.SearchResults = [Card("INT-2", "Intervention B")];
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.False(vm.IsOffline);
|
|
Assert.Single(vm.Items);
|
|
Assert.Null(vm.ErrorMessage);
|
|
Assert.False(vm.IsEmptyRecent);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ShowPinned_AfficheFavoris()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
Pinned = [Card("INT-9", "Favori", isPinned: true)]
|
|
};
|
|
var vm = CreateVm(service);
|
|
|
|
await vm.ShowPinnedCommand.ExecuteAsync(null);
|
|
|
|
Assert.True(vm.IsShowingPinned);
|
|
Assert.Single(vm.Items);
|
|
Assert.Equal("Épinglées", vm.TabLabel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TogglePinned_Bascule()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
Pinned = [Card("INT-9", "Favori", isPinned: true)]
|
|
};
|
|
var vm = CreateVm(service);
|
|
await vm.ShowPinnedCommand.ExecuteAsync(null);
|
|
|
|
await vm.TogglePinnedCommand.ExecuteAsync(vm.Items[0]);
|
|
|
|
Assert.Empty(service.Pinned);
|
|
Assert.Empty(vm.Items);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TogglePinned_AppliqueImmediatement_SansRechargerLaListe()
|
|
{
|
|
var service = new FakeInterventionService
|
|
{
|
|
LastSearch = [Card("INT-1", "Intervention A"), Card("INT-2", "Intervention B")]
|
|
};
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
// La collection affichée sert de témoin : un rechargement la remplacerait.
|
|
var collection = vm.Items;
|
|
var item = vm.Items[0];
|
|
|
|
await vm.TogglePinnedCommand.ExecuteAsync(item);
|
|
|
|
Assert.True(item.IsPinned);
|
|
Assert.Same(collection, vm.Items);
|
|
Assert.Equal(2, vm.Items.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Search_ClientSearch_EnvoieLeTexte()
|
|
{
|
|
var service = new FakeInterventionService();
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
vm.ClientSearch = "Alpha";
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal("Alpha", service.LastRequest?.ClientSearch);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Search_Reference105Tous_EnvoieCriteresNeutres()
|
|
{
|
|
var service = new FakeInterventionService();
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
vm.ReferenceSearch = "105";
|
|
vm.ClientSearch = string.Empty;
|
|
vm.SiteSearch = string.Empty;
|
|
vm.CollaborateurSearch = string.Empty;
|
|
vm.SelectedEtatIndex = 0;
|
|
vm.SelectedTypeIndex = 0;
|
|
vm.RendezVousRelation = 0;
|
|
vm.RendezVousDate = null;
|
|
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal("105", service.LastRequest?.Reference);
|
|
Assert.Null(service.LastRequest?.ClientSearch);
|
|
Assert.Null(service.LastRequest?.SiteSearch);
|
|
Assert.Null(service.LastRequest?.CollaborateurSearch);
|
|
Assert.Null(service.LastRequest?.AvecRendezVous);
|
|
Assert.Null(service.LastRequest?.RendezVousDate);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Search_SiteSearch_EnvoieLeTexte()
|
|
{
|
|
var service = new FakeInterventionService();
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
vm.SiteSearch = "Nord";
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal("Nord", service.LastRequest?.SiteSearch);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Search_CollaborateurSearch_EnvoieLeTexte()
|
|
{
|
|
var service = new FakeInterventionService();
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
vm.CollaborateurSearch = "Dupont";
|
|
await vm.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.Equal("Dupont", service.LastRequest?.CollaborateurSearch);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Reset_RemetToutesLesSelectionsAZero()
|
|
{
|
|
var service = new FakeInterventionService();
|
|
var vm = CreateVm(service);
|
|
await vm.LoadCommand.ExecuteAsync(null);
|
|
|
|
vm.ReferenceSearch = "INT";
|
|
vm.ClientSearch = "Alpha";
|
|
vm.SiteSearch = "Nord";
|
|
vm.CollaborateurSearch = "Dupont";
|
|
vm.SelectedEtatIndex = 1;
|
|
vm.SelectedTypeIndex = 1;
|
|
vm.RendezVousRelation = 1;
|
|
vm.RendezVousDate = DateTime.Now;
|
|
|
|
vm.ResetSearchCommand.Execute(null);
|
|
|
|
Assert.Empty(vm.ReferenceSearch);
|
|
Assert.Empty(vm.ClientSearch);
|
|
Assert.Empty(vm.SiteSearch);
|
|
Assert.Empty(vm.CollaborateurSearch);
|
|
Assert.Equal(0, vm.SelectedEtatIndex);
|
|
Assert.Equal(0, vm.SelectedTypeIndex);
|
|
Assert.Equal(0, vm.RendezVousRelation);
|
|
Assert.Null(vm.RendezVousDate);
|
|
}
|
|
|
|
private static InterventionCardItem Card(string reference, string titre, bool isPinned = false)
|
|
=> new InterventionCardItem
|
|
{
|
|
InterventionGuid = Guid.NewGuid(),
|
|
Reference = reference,
|
|
Titre = titre,
|
|
IsPinned = isPinned
|
|
};
|
|
|
|
private sealed class FakeInterventionService : IInterventionLocalService
|
|
{
|
|
public List<InterventionCardItem> LastSearch { get; set; } = [];
|
|
public List<InterventionCardItem> Pinned { get; set; } = [];
|
|
public List<InterventionCardItem> SearchResults { get; set; } = [];
|
|
public Exception? SearchException { get; set; }
|
|
public MobileInterventionSearchRequest? LastRequest { get; set; }
|
|
|
|
public Task<Guid> CreateAsync(InterventionSyncPayload payload, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<Guid> UpdateAsync(Guid interventionGuid, InterventionSyncPayload payload, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<Guid> ArchiveAsync(Guid interventionGuid, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<InterventionDetail?> GetInterventionByIdAsync(Guid interventionGuid, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<InterventionDetail?>(null);
|
|
|
|
public Task<IReadOnlyList<InterventionListItem>> GetInterventionsAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<InterventionListItem>>([]);
|
|
|
|
public async Task<IReadOnlyList<InterventionCardItem>> SearchAsync(MobileInterventionSearchRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
LastRequest = request;
|
|
if (SearchException is not null)
|
|
{
|
|
throw SearchException;
|
|
}
|
|
|
|
await Task.CompletedTask;
|
|
LastSearch = SearchResults;
|
|
return LastSearch;
|
|
}
|
|
|
|
public Task<IReadOnlyList<InterventionCardItem>> GetLastSearchAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<InterventionCardItem>>(LastSearch);
|
|
|
|
public Task<IReadOnlyList<InterventionCardItem>> GetPinnedAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<InterventionCardItem>>(Pinned);
|
|
|
|
public Task SetPinnedAsync(Guid interventionGuid, bool isPinned, CancellationToken cancellationToken = default)
|
|
{
|
|
InterventionCardItem? item = Pinned.FirstOrDefault(i => i.InterventionGuid == interventionGuid);
|
|
if (item is not null && !isPinned)
|
|
{
|
|
Pinned.Remove(item);
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
public Task RefreshPinnedAsync(CancellationToken cancellationToken = default)
|
|
=> Task.CompletedTask;
|
|
|
|
public Task RefreshInterventionAsync(Guid interventionGuid, CancellationToken cancellationToken = default)
|
|
=> Task.CompletedTask;
|
|
}
|
|
|
|
private sealed class FakeParametreMetierService : IParametreMetierLocalService
|
|
{
|
|
public Task<IReadOnlyList<ReferenceValueDto>> GetTypeTempsAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<ReferenceValueDto>>([]);
|
|
|
|
public Task<Omika.Core.Contracts.Configuration.TypeTempsParametresDto?> GetTypeTempsParametresAsync(string typeTempsKey, CancellationToken ct = default)
|
|
=> Task.FromResult<Omika.Core.Contracts.Configuration.TypeTempsParametresDto?>(null);
|
|
|
|
public Task<Omika.Core.Contracts.Configuration.TypeRdvParametresDto?> GetTypeRdvParametresAsync(string typeRdvKey, CancellationToken ct = default)
|
|
=> Task.FromResult<Omika.Core.Contracts.Configuration.TypeRdvParametresDto?>(null);
|
|
|
|
public Task<Omika.Core.Contracts.Configuration.StatutRdvParametresDto?> GetStatutRdvParametresAsync(string statutRdvKey, CancellationToken ct = default)
|
|
=> Task.FromResult<Omika.Core.Contracts.Configuration.StatutRdvParametresDto?>(null);
|
|
|
|
public Task<IReadOnlyList<ReferenceValueDto>> GetInterventionTypesAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<ReferenceValueDto>>([]);
|
|
|
|
public Task<IReadOnlyList<ReferenceValueDto>> GetInterventionEtatsAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<ReferenceValueDto>>([]);
|
|
|
|
public Task<IReadOnlyList<ReferenceValueDto>> GetDossierTypesAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<ReferenceValueDto>>([]);
|
|
|
|
public Task<IReadOnlyList<ReferenceValueDto>> GetDossierStatutsAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<ReferenceValueDto>>([]);
|
|
|
|
public Task<IReadOnlyList<ReferenceValueDto>> GetClientTypesAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<ReferenceValueDto>>([]);
|
|
|
|
public Task<IReadOnlyList<ReferenceValueDto>> GetClientStatutsAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<ReferenceValueDto>>([]);
|
|
|
|
public Task<IReadOnlyList<ReferenceValueDto>> GetSiteTypesAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<ReferenceValueDto>>([]);
|
|
|
|
public Task<IReadOnlyList<ReferenceValueDto>> GetTypeSignatairesAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<ReferenceValueDto>>([]);
|
|
}
|
|
|
|
private sealed class FakeClientService : IClientLocalService
|
|
{
|
|
public Task<IReadOnlyList<ClientListItem>> GetClientsAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<ClientListItem>>(
|
|
new List<ClientListItem>
|
|
{
|
|
new() { ClientGuid = Guid.NewGuid(), Nom = "Client Alpha", Code = "C001" }
|
|
});
|
|
|
|
public Task<ClientDetail?> GetClientByIdAsync(Guid clientGuid, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<ClientDetail?>(null);
|
|
}
|
|
|
|
private sealed class FakeSiteService : ISiteLocalService
|
|
{
|
|
public Task<IReadOnlyList<SiteListItem>> GetSitesAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<SiteListItem>>(
|
|
new List<SiteListItem>
|
|
{
|
|
new() { SiteGuid = Guid.NewGuid(), Nom = "Site Nord", Code = "S001" }
|
|
});
|
|
|
|
public Task<SiteDetail?> GetSiteByIdAsync(Guid siteGuid, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<SiteDetail?>(null);
|
|
}
|
|
|
|
private sealed class FakeCollaborateurService : ICollaborateurLocalService
|
|
{
|
|
public Task<IReadOnlyList<CollaborateurListItem>> GetCollaborateursAsync(CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<CollaborateurListItem>>(
|
|
new List<CollaborateurListItem>
|
|
{
|
|
new() { CollaborateurGuid = Guid.NewGuid(), DisplayName = "Jean Dupont", Code = "COL-1" }
|
|
});
|
|
|
|
public Task<CollaborateurDetail?> GetCollaborateurByIdAsync(Guid collaborateurGuid, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<CollaborateurDetail?>(null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Seul événement de diagnostic attribué au ViewModel Interventions : la télémétrie
|
|
/// d'usage (fonctionnalités) est un <see cref="TelemetryEvent"/>, pas un
|
|
/// <see cref="DiagnosticEvent"/>. Le filtre porte sur le composant, la télémétrie
|
|
/// statique globale étant partagée avec les classes exécutées en parallèle.
|
|
/// </summary>
|
|
private static DiagnosticEvent EvenementInterventions(CapturingTelemetry telemetry) =>
|
|
Assert.Single(
|
|
telemetry.Events.OfType<DiagnosticEvent>(),
|
|
e => e.Component == nameof(InterventionsViewModel));
|
|
|
|
/// <summary>Aucun événement de diagnostic de cet écran en Error/Critical.</summary>
|
|
private static void AssertAucunIncident(CapturingTelemetry telemetry) =>
|
|
Assert.DoesNotContain(
|
|
telemetry.Events.OfType<DiagnosticEvent>(),
|
|
e => e.Component == nameof(InterventionsViewModel) && e.Level == TelemetryLevel.Error);
|
|
|
|
/// <summary>
|
|
/// Capture en mémoire des événements de la télémétrie statique globale
|
|
/// (<see cref="ExceptionTelemetry.Initialize"/>), rétablie à <c>null</c> après
|
|
/// chaque test — la classe appartient à la collection « TelemetrieStatique ».
|
|
/// </summary>
|
|
private sealed class CapturingTelemetry : IOmikaTelemetry
|
|
{
|
|
public List<OmikaEvent> Events { get; } = [];
|
|
|
|
public void Track(TelemetryEvent telemetryEvent) => Events.Add(telemetryEvent);
|
|
|
|
public void Log(DiagnosticEvent diagnosticEvent) => Events.Add(diagnosticEvent);
|
|
}
|
|
}
|