546 lines
22 KiB
C#
546 lines
22 KiB
C#
using System.Net.Http;
|
|
using System.Text.Json;
|
|
using Omika.App.Local.Abstractions;
|
|
using Omika.App.Local.Models;
|
|
using Omika.App.Local.Services;
|
|
using Omika.App.Navigation;
|
|
using Omika.App.ViewModels;
|
|
using Omika.Core.Contracts.Clients;
|
|
using Omika.Core.Contracts.Common;
|
|
using Omika.Core.Contracts.Interventions;
|
|
using Omika.Core.Observability;
|
|
|
|
namespace Omika.Tests.Mobile.Local;
|
|
|
|
/// <summary>
|
|
/// Tests du ViewModel de la liste des dossiers : chargement, états vide/erreur,
|
|
/// six critères de recherche (Titre, Client, Statut, Type, Référence externe,
|
|
/// Référence interne), épinglage local, reset et navigation. La recherche est
|
|
/// déléguée au service (côté serveur) : le fake filtre les données locales
|
|
/// comme le ferait la requête SQL LIKE.
|
|
///
|
|
/// Couvre aussi la politique d'erreur de la recherche : elle atteint l'API sans
|
|
/// garde intermédiaire (<c>DossierLocalService.SearchDossiersAsync</c> →
|
|
/// <c>_apiClient</c>), donc un échec réseau — ticket de production du 14/10,
|
|
/// « Connection failure » — doit partir en Warning/P2, jamais en Error/P1.
|
|
/// </summary>
|
|
[Collection("TelemetrieStatique")]
|
|
public class DossiersViewModelTests : LocalTestBase
|
|
{
|
|
private readonly DossiersViewModel _viewModel;
|
|
private readonly FakeDossierService _service;
|
|
private readonly StubParametreMetierService _parametreMetier = new();
|
|
private readonly NavigationService _navigation;
|
|
|
|
public DossiersViewModelTests()
|
|
{
|
|
var inner = new DossierLocalService(DossierRepository, ClientRepository, DossierStateRepository, DossierInterventionCacheRepository, ApiClient);
|
|
_service = new FakeDossierService(inner);
|
|
_navigation = new NavigationService();
|
|
_viewModel = new DossiersViewModel(_service, _parametreMetier, _navigation);
|
|
}
|
|
|
|
private static LocalDossier CreateDossier(
|
|
Guid id,
|
|
string reference,
|
|
string titre,
|
|
Guid? clientId = null,
|
|
string? statut = null,
|
|
string? type = null,
|
|
string? refInterne = null,
|
|
string? refExterne = null,
|
|
int nbInterventions = 0)
|
|
{
|
|
var dto = new DossierDetailsDto
|
|
{
|
|
Id = id.ToString(),
|
|
ReferenceMetier = reference,
|
|
Titre = titre,
|
|
ClientId = clientId?.ToString(),
|
|
Statut = statut is null ? null : new ReferenceValueDto { Label = statut },
|
|
Type = type is null ? null : new ReferenceValueDto { Label = type },
|
|
RefInterne = refInterne,
|
|
RefExterne = refExterne,
|
|
NbInterventionsNonArchivees = nbInterventions
|
|
};
|
|
return new LocalDossier
|
|
{
|
|
Id = id,
|
|
ClientId = clientId,
|
|
PayloadJson = JsonSerializer.Serialize(dto),
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
};
|
|
}
|
|
|
|
private static LocalClient CreateClient(Guid id, string nom)
|
|
{
|
|
var dto = new ClientDetailsDto { Id = id.ToString(), Nom = nom };
|
|
return new LocalClient
|
|
{
|
|
Id = id,
|
|
PayloadJson = JsonSerializer.Serialize(dto),
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
};
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Load_NAfficheAucunResultatAvantRecherche()
|
|
{
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A"));
|
|
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
Assert.False(_viewModel.IsLoading);
|
|
Assert.False(_viewModel.HasDossiers);
|
|
Assert.Empty(_viewModel.Dossiers);
|
|
Assert.False(_viewModel.HasSearched);
|
|
Assert.True(_viewModel.IsSearchNotStarted);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Load_AucunDossier_Empty()
|
|
{
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
Assert.False(_viewModel.IsLoading);
|
|
Assert.False(_viewModel.HasDossiers);
|
|
Assert.True(_viewModel.IsSearchNotStarted);
|
|
Assert.Null(_viewModel.ErrorMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FiltreTitre_InsensibleCasse()
|
|
{
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "Alpha"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "Beta"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
_viewModel.TitreText = "alpha";
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
var item = Assert.Single(_viewModel.Dossiers);
|
|
Assert.Equal("Alpha", item.Titre);
|
|
Assert.Equal("alpha", _service.LastTitre);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FiltreStatut()
|
|
{
|
|
_parametreMetier.DossierStatuts = [new() { Label = "Ouvert" }, new() { Label = "Clôturé" }];
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A", statut: "Ouvert"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "B", statut: "Clôturé"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
_viewModel.SelectedStatutIndex = _viewModel.StatutOptions.ToList().IndexOf("Ouvert");
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
var item = Assert.Single(_viewModel.Dossiers);
|
|
Assert.Equal("DOS-001", item.Reference);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FiltreType()
|
|
{
|
|
_parametreMetier.DossierTypes = [new() { Label = "Maintenance" }, new() { Label = "Installation" }];
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A", type: "Maintenance"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "B", type: "Installation"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
_viewModel.SelectedTypeIndex = _viewModel.TypeOptions.ToList().IndexOf("Installation");
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
var item = Assert.Single(_viewModel.Dossiers);
|
|
Assert.Equal("DOS-002", item.Reference);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FiltreClient_ParNomHumain()
|
|
{
|
|
Guid clientAlpha = Guid.NewGuid();
|
|
Guid clientBeta = Guid.NewGuid();
|
|
await ClientRepository.UpsertAsync(CreateClient(clientAlpha, "Client Alpha"));
|
|
await ClientRepository.UpsertAsync(CreateClient(clientBeta, "Client Beta"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A", clientId: clientAlpha));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "B", clientId: clientBeta));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
_viewModel.ClientText = "Client Alpha";
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
var item = Assert.Single(_viewModel.Dossiers);
|
|
Assert.Equal("DOS-001", item.Reference);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FiltreRefInterne()
|
|
{
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A", refInterne: "INT-42"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "B", refInterne: "INT-7"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
_viewModel.RefInterneText = "42";
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
var item = Assert.Single(_viewModel.Dossiers);
|
|
Assert.Equal("DOS-001", item.Reference);
|
|
Assert.Equal("42", _service.LastRefInterne);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FiltreRefExterne()
|
|
{
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A", refExterne: "EXT-42"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "B", refExterne: "EXT-7"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
_viewModel.RefExterneText = "42";
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
var item = Assert.Single(_viewModel.Dossiers);
|
|
Assert.Equal("DOS-001", item.Reference);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Reset_RetourneEtatInitialSansResultat()
|
|
{
|
|
_parametreMetier.DossierStatuts = [new() { Label = "Ouvert" }, new() { Label = "Clôturé" }];
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "Alpha", statut: "Ouvert"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "Beta", statut: "Clôturé"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
_viewModel.TitreText = "Alpha";
|
|
_viewModel.SelectedStatutIndex = _viewModel.StatutOptions.ToList().IndexOf("Ouvert");
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
Assert.Single(_viewModel.Dossiers);
|
|
Assert.True(_viewModel.HasActiveFilters);
|
|
|
|
_viewModel.ResetFiltersCommand.Execute(null);
|
|
|
|
Assert.Empty(_viewModel.Dossiers);
|
|
Assert.False(_viewModel.HasSearched);
|
|
Assert.True(_viewModel.IsSearchNotStarted);
|
|
Assert.False(_viewModel.HasActiveFilters);
|
|
Assert.Empty(_viewModel.TitreText);
|
|
Assert.Equal(0, _viewModel.SelectedStatutIndex);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Pin_Persiste_EtFiltreEpingles()
|
|
{
|
|
Guid id = Guid.NewGuid();
|
|
await DossierRepository.UpsertAsync(CreateDossier(id, "DOS-001", "A"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "B"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
// Épingler DOS-001.
|
|
var item = _viewModel.Dossiers.First(d => d.Reference == "DOS-001");
|
|
await _viewModel.TogglePinnedCommand.ExecuteAsync(item);
|
|
|
|
// L'état épinglé est persisté (relecture depuis le repository local).
|
|
var pinned = await DossierStateRepository.GetPinnedIdsAsync();
|
|
Assert.Contains(id, pinned);
|
|
|
|
// Onglet « Épinglés » : seule DOS-001 reste.
|
|
_viewModel.ShowPinnedCommand.Execute(null);
|
|
var only = Assert.Single(_viewModel.Dossiers);
|
|
Assert.Equal("DOS-001", only.Reference);
|
|
|
|
// Retour « Récentes » : les résultats serveur (2) sont restaurés.
|
|
_viewModel.ShowRecentCommand.Execute(null);
|
|
Assert.Equal(2, _viewModel.Dossiers.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TogglePinned_AppliqueImmediatement_SansRechargerLaListe()
|
|
{
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "Alpha"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "Beta"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
// La collection affichée sert de témoin : un rechargement la remplacerait.
|
|
var collection = _viewModel.Dossiers;
|
|
var item = _viewModel.Dossiers[0];
|
|
|
|
await _viewModel.TogglePinnedCommand.ExecuteAsync(item);
|
|
|
|
Assert.True(item.IsPinned);
|
|
Assert.Same(collection, _viewModel.Dossiers);
|
|
Assert.Equal(2, _viewModel.Dossiers.Count);
|
|
|
|
// Et l'état est bien persisté (relecture depuis le repository local).
|
|
Assert.Contains(item.DossierGuid, await DossierStateRepository.GetPinnedIdsAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EtatVide_SansDonneesEtSansResultat_AfficheAucunResultat()
|
|
{
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
Assert.True(_viewModel.IsSearchNotStarted);
|
|
Assert.False(_viewModel.HasNoResults);
|
|
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
Assert.True(_viewModel.HasSearched);
|
|
Assert.True(_viewModel.HasNoResults);
|
|
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "A"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
_viewModel.TitreText = "introuvable";
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.True(_viewModel.HasSearched);
|
|
Assert.True(_viewModel.HasNoResults);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ChangementCritereSansRecherche_NeModifiePasLaListe()
|
|
{
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "Alpha"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "Beta"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
_viewModel.TitreText = "Alpha";
|
|
|
|
Assert.False(_viewModel.HasSearched);
|
|
Assert.Empty(_viewModel.Dossiers);
|
|
Assert.True(_viewModel.IsSearchNotStarted);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RechercheVide_AfficheTousLesNonArchives()
|
|
{
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-001", "Alpha"));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "Beta"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
Assert.True(_viewModel.HasSearched);
|
|
Assert.Equal(2, _viewModel.Dossiers.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EpinglesSansRecherche_AfficheFavorisPuisRetourEtatInitial()
|
|
{
|
|
Guid id = Guid.NewGuid();
|
|
await DossierRepository.UpsertAsync(CreateDossier(id, "DOS-001", "Alpha"));
|
|
await DossierStateRepository.SetPinnedAsync(
|
|
id,
|
|
true,
|
|
JsonSerializer.Serialize(new DossierListItem { DossierGuid = id, Reference = "DOS-001", Titre = "Alpha" }));
|
|
await DossierRepository.UpsertAsync(CreateDossier(Guid.NewGuid(), "DOS-002", "Beta"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
_viewModel.ShowPinnedCommand.Execute(null);
|
|
|
|
var only = Assert.Single(_viewModel.Dossiers);
|
|
Assert.Equal("DOS-001", only.Reference);
|
|
|
|
_viewModel.ShowRecentCommand.Execute(null);
|
|
|
|
Assert.Empty(_viewModel.Dossiers);
|
|
Assert.False(_viewModel.HasSearched);
|
|
Assert.True(_viewModel.IsSearchNotStarted);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CardMapping_TypeAbsentMasqueEtReferencesPropagees()
|
|
{
|
|
await DossierRepository.UpsertAsync(CreateDossier(
|
|
Guid.NewGuid(),
|
|
"DOS-001",
|
|
"",
|
|
type: "Aucun Type de dossier",
|
|
refInterne: "INT-42",
|
|
refExterne: "EXT-42"));
|
|
await _viewModel.LoadCommand.ExecuteAsync(null);
|
|
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
|
|
var item = Assert.Single(_viewModel.Dossiers);
|
|
Assert.Null(item.Type);
|
|
Assert.Null(item.Titre);
|
|
Assert.Equal("INT-42", item.RefInterne);
|
|
Assert.Equal("EXT-42", item.RefExterne);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fake de service : délègue au service local réel (lecture SQLite) mais
|
|
/// implémente <see cref="SearchDossiersAsync"/> par un filtre en mémoire des
|
|
/// données locales, simulant la requête SQL LIKE côté serveur.
|
|
/// </summary>
|
|
private sealed class FakeDossierService : IDossierLocalService
|
|
{
|
|
private readonly IDossierLocalService _inner;
|
|
|
|
public string? LastRefInterne { get; private set; }
|
|
public string? LastTitre { get; private set; }
|
|
public string? LastClient { get; private set; }
|
|
public string? LastStatut { get; private set; }
|
|
public string? LastType { get; private set; }
|
|
public string? LastRefExterne { get; private set; }
|
|
|
|
public FakeDossierService(IDossierLocalService inner) => _inner = inner;
|
|
|
|
public Task<IReadOnlyList<DossierListItem>> GetDossiersAsync(CancellationToken ct = default)
|
|
=> _inner.GetDossiersAsync(ct);
|
|
|
|
/// <summary>Simule un échec de la recherche serveur (réseau ou métier).</summary>
|
|
public Exception? SearchException { get; set; }
|
|
|
|
public async Task<IReadOnlyList<DossierListItem>> SearchDossiersAsync(
|
|
string? refInterne,
|
|
string? titre,
|
|
string? client,
|
|
string? statut,
|
|
string? type,
|
|
string? refExterne,
|
|
CancellationToken ct = default)
|
|
{
|
|
if (SearchException is not null)
|
|
{
|
|
throw SearchException;
|
|
}
|
|
|
|
LastRefInterne = refInterne;
|
|
LastTitre = titre;
|
|
LastClient = client;
|
|
LastStatut = statut;
|
|
LastType = type;
|
|
LastRefExterne = refExterne;
|
|
|
|
IReadOnlyList<DossierListItem> all = await _inner.GetDossiersAsync(ct);
|
|
IEnumerable<DossierListItem> filtered = all;
|
|
|
|
if (!string.IsNullOrWhiteSpace(refInterne))
|
|
{
|
|
filtered = filtered.Where(d => Contains(d.RefInterne, refInterne));
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(titre))
|
|
{
|
|
filtered = filtered.Where(d => Contains(d.Titre, titre));
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(client))
|
|
{
|
|
filtered = filtered.Where(d => Contains(d.ClientNom, client));
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(statut))
|
|
{
|
|
filtered = filtered.Where(d => string.Equals(d.Statut, statut, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(type))
|
|
{
|
|
filtered = filtered.Where(d => string.Equals(d.Type, type, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
if (!string.IsNullOrWhiteSpace(refExterne))
|
|
{
|
|
filtered = filtered.Where(d => Contains(d.RefExterne, refExterne));
|
|
}
|
|
|
|
return filtered.ToList();
|
|
}
|
|
|
|
public Task<IReadOnlyList<DossierListItem>> GetLastSearchAsync(CancellationToken ct = default)
|
|
=> _inner.GetLastSearchAsync(ct);
|
|
|
|
public Task ClearLastSearchAsync(CancellationToken ct = default)
|
|
=> _inner.ClearLastSearchAsync(ct);
|
|
|
|
private static bool Contains(string? value, string query)
|
|
=> value is not null && value.Contains(query, StringComparison.OrdinalIgnoreCase);
|
|
|
|
public Task<DossierDetail?> GetDossierByIdAsync(Guid dossierGuid, CancellationToken ct = default)
|
|
=> _inner.GetDossierByIdAsync(dossierGuid, ct);
|
|
|
|
public Task SetPinnedAsync(Guid dossierGuid, bool isPinned, DossierListItem? item, CancellationToken ct = default)
|
|
=> _inner.SetPinnedAsync(dossierGuid, isPinned, item, ct);
|
|
|
|
public Task<IReadOnlyList<DossierListItem>> GetPinnedAsync(CancellationToken ct = default)
|
|
=> _inner.GetPinnedAsync(ct);
|
|
|
|
public Task<IReadOnlyList<DossierIntervention>> GetDossierInterventionsAsync(Guid dossierGuid, CancellationToken ct = default)
|
|
=> _inner.GetDossierInterventionsAsync(dossierGuid, ct);
|
|
|
|
public Task<IReadOnlyList<DossierIntervention>> RefreshDossierInterventionsAsync(Guid dossierGuid, CancellationToken ct = default)
|
|
=> _inner.RefreshDossierInterventionsAsync(dossierGuid, ct);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ticket de production du 14/10 (« Connection failure ») : l'échec réseau de la
|
|
/// recherche n'est pas un incident. L'événement part en Warning/P2, l'écran n'entre
|
|
/// dans aucun groupe d'erreurs du Diagnostic Center, et l'utilisateur reçoit le
|
|
/// libellé français stable plutôt qu'un message technique anglais.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Search_EchecReseau_SignaleEnWarningSansIncident()
|
|
{
|
|
_service.SearchException = new HttpRequestException("Connection failure");
|
|
|
|
var telemetry = new CapturingTelemetry();
|
|
ExceptionTelemetry.Initialize(telemetry, null);
|
|
try
|
|
{
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
}
|
|
finally
|
|
{
|
|
ExceptionTelemetry.Initialize(null, null);
|
|
}
|
|
|
|
Assert.Equal("Hors ligne — impossible de contacter le serveur.", _viewModel.ErrorMessage);
|
|
|
|
DiagnosticEvent evt = EvenementDossiers(telemetry);
|
|
Assert.Equal(TelemetryLevel.Warning, evt.Level);
|
|
Assert.Equal("HttpRequestException", evt.ErrorCode);
|
|
AssertAucunIncident(telemetry);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Contre-épreuve : une erreur métier (hors réseau) reste un incident Error/P1.
|
|
/// Le classement ne dégrade pas les vraies erreurs.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Search_ErreurMetier_ResteUnIncident()
|
|
{
|
|
_service.SearchException = new InvalidOperationException("échec de recherche");
|
|
|
|
var telemetry = new CapturingTelemetry();
|
|
ExceptionTelemetry.Initialize(telemetry, null);
|
|
try
|
|
{
|
|
await _viewModel.SearchCommand.ExecuteAsync(null);
|
|
}
|
|
finally
|
|
{
|
|
ExceptionTelemetry.Initialize(null, null);
|
|
}
|
|
|
|
DiagnosticEvent evt = EvenementDossiers(telemetry);
|
|
Assert.Equal(TelemetryLevel.Error, evt.Level);
|
|
Assert.Equal("InvalidOperationException", evt.ErrorCode);
|
|
}
|
|
|
|
/// <summary>Seul événement de diagnostic attribué à ce ViewModel (l'usage est un TelemetryEvent).</summary>
|
|
private static DiagnosticEvent EvenementDossiers(CapturingTelemetry telemetry) =>
|
|
Assert.Single(
|
|
telemetry.Events.OfType<DiagnosticEvent>(),
|
|
e => e.Component == nameof(DossiersViewModel));
|
|
|
|
/// <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(DossiersViewModel) && e.Level == TelemetryLevel.Error);
|
|
|
|
/// <summary>Capture des événements de la télémétrie statique globale.</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);
|
|
}
|
|
}
|