Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,649 @@
|
||||
using System.Text.Json;
|
||||
using Omika.App.Local.Abstractions;
|
||||
using Omika.App.Local.Models;
|
||||
using Omika.App.Local.Services;
|
||||
using Omika.Core.Contracts.Clients;
|
||||
using Omika.Core.Contracts.Common;
|
||||
using Omika.Core.Contracts.Interventions;
|
||||
using Omika.Core.Contracts.Organisation;
|
||||
using Omika.Core.Contracts.Planning;
|
||||
using Omika.Core.Contracts.Sites;
|
||||
|
||||
namespace Omika.Tests.Mobile.Local;
|
||||
|
||||
/// <summary>
|
||||
/// Tests du service Agenda : lecture locale SQLite, filtrage par journée,
|
||||
/// enrichissement Client/Site/Intervention, tri chronologique et détail.
|
||||
/// </summary>
|
||||
public class AgendaServiceTests : LocalTestBase
|
||||
{
|
||||
private readonly AgendaService _service;
|
||||
|
||||
public AgendaServiceTests()
|
||||
{
|
||||
_service = new AgendaService(
|
||||
RendezVousRepository, ClientRepository, SiteRepository, InterventionRepository, CollaborateurRepository, ApiClient, SessionService);
|
||||
}
|
||||
|
||||
private static string Serialize<T>(T value) => JsonSerializer.Serialize(value);
|
||||
|
||||
private static LocalRendezVous CreateRendezVous(
|
||||
Guid id,
|
||||
DateTime debut,
|
||||
DateTime fin,
|
||||
string? clientId = null,
|
||||
string? siteId = null,
|
||||
string? interventionId = null,
|
||||
string? titre = "Rendez-vous",
|
||||
string? statut = "Planifié",
|
||||
string? typeActivite = "Visite",
|
||||
bool estAnnule = false)
|
||||
{
|
||||
var dto = new RendezVousDetailsDto
|
||||
{
|
||||
Id = id.ToString(),
|
||||
Titre = titre,
|
||||
Debut = debut,
|
||||
Fin = fin,
|
||||
ClientId = clientId,
|
||||
SiteId = siteId,
|
||||
InterventionId = interventionId,
|
||||
Statut = statut is null ? null : new ReferenceValueDto { Label = statut },
|
||||
TypeActivite = typeActivite is null ? null : new ReferenceValueDto { Label = typeActivite },
|
||||
Annulation = estAnnule ? new AnnulationRendezVousDto { Motif = new ReferenceValueDto { Label = "Annulé" } } : null
|
||||
};
|
||||
return new LocalRendezVous
|
||||
{
|
||||
Id = id,
|
||||
PayloadJson = 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 = Serialize(dto), UpdatedAtUtc = DateTimeOffset.UtcNow };
|
||||
}
|
||||
|
||||
private static LocalSite CreateSite(Guid id, string nom, Guid clientId)
|
||||
{
|
||||
var dto = new SiteDetailsDto { Id = id.ToString(), Nom = nom, ClientId = clientId.ToString() };
|
||||
return new LocalSite { Id = id, ClientId = clientId, PayloadJson = Serialize(dto), UpdatedAtUtc = DateTimeOffset.UtcNow };
|
||||
}
|
||||
|
||||
private static LocalIntervention CreateIntervention(
|
||||
Guid id, string reference, Guid clientId, Guid siteId,
|
||||
string? referenceExterne = null, string? numeroDevis = null)
|
||||
{
|
||||
var dto = new InterventionDetailsDto
|
||||
{
|
||||
Id = id.ToString(),
|
||||
Reference = reference,
|
||||
ReferenceExterne = referenceExterne,
|
||||
NumeroDevis = numeroDevis,
|
||||
ClientId = clientId.ToString(),
|
||||
SiteId = siteId.ToString()
|
||||
};
|
||||
return new LocalIntervention
|
||||
{
|
||||
Id = id,
|
||||
ClientId = clientId,
|
||||
SiteId = siteId,
|
||||
PayloadJson = Serialize(dto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task JourSansRendezVous_RetourneListeVide()
|
||||
{
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(DateTime.Today);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FiltreParJournee_IgnoreAutresJours()
|
||||
{
|
||||
// Arrange
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
await RendezVousRepository.UpsertAsync(CreateRendezVous(Guid.NewGuid(), jour.AddHours(9), jour.AddHours(10)));
|
||||
await RendezVousRepository.UpsertAsync(CreateRendezVous(Guid.NewGuid(), jour.AddDays(1).AddHours(9), jour.AddDays(1).AddHours(10)));
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : seul le rendez-vous du jour ciblé est retourné.
|
||||
Assert.Single(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TriChronologique_ParHeureDebut()
|
||||
{
|
||||
// Arrange
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
Guid tard = Guid.NewGuid();
|
||||
Guid tot = Guid.NewGuid();
|
||||
await RendezVousRepository.UpsertAsync(CreateRendezVous(tard, jour.AddHours(14), jour.AddHours(15), titre: "Tard"));
|
||||
await RendezVousRepository.UpsertAsync(CreateRendezVous(tot, jour.AddHours(8), jour.AddHours(9), titre: "Tôt"));
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : trié par heure de début croissante.
|
||||
Assert.Equal(2, result.Count);
|
||||
Assert.Equal("Tôt", result[0].Titre);
|
||||
Assert.Equal("Tard", result[1].Titre);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Enrichissement_ClientSiteIntervention()
|
||||
{
|
||||
// Arrange
|
||||
Guid clientId = Guid.NewGuid();
|
||||
Guid siteId = Guid.NewGuid();
|
||||
Guid interventionId = Guid.NewGuid();
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
|
||||
await ClientRepository.UpsertAsync(CreateClient(clientId, "Client Alpha"));
|
||||
await SiteRepository.UpsertAsync(CreateSite(siteId, "Site Beta", clientId));
|
||||
await InterventionRepository.UpsertAsync(CreateIntervention(
|
||||
interventionId, "INT-001", clientId, siteId, "EXT-7", "DEV-42"));
|
||||
await RendezVousRepository.UpsertAsync(CreateRendezVous(
|
||||
rdvId, jour.AddHours(9), jour.AddHours(10),
|
||||
clientId.ToString(), siteId.ToString(), interventionId.ToString()));
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : les références sont enrichies.
|
||||
AgendaItem item = Assert.Single(result);
|
||||
Assert.Equal("Client Alpha", item.ClientNom);
|
||||
Assert.Equal("Site Beta", item.SiteNom);
|
||||
Assert.Equal("INT-001", item.InterventionReference);
|
||||
Assert.Equal("EXT-7", item.InterventionReferenceExterne);
|
||||
Assert.Equal("DEV-42", item.InterventionNumeroDevis);
|
||||
Assert.Equal("Visite", item.TypeActivite);
|
||||
Assert.Equal("Planifié", item.Statut);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Adresse_Provient_Du_Site_De_LIntervention()
|
||||
{
|
||||
// Arrange : le rendez-vous n'a pas de site direct ; le nom et l'adresse
|
||||
// doivent être résolus depuis le site de l'intervention liée.
|
||||
Guid clientId = Guid.NewGuid();
|
||||
Guid siteInterventionId = Guid.NewGuid();
|
||||
Guid interventionId = Guid.NewGuid();
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
|
||||
var siteDto = new SiteDetailsDto
|
||||
{
|
||||
Id = siteInterventionId.ToString(),
|
||||
Nom = "Chantier Intervention",
|
||||
ClientId = clientId.ToString(),
|
||||
Adresse = new AddressDto { OneLine = "12 rue du Test, 75000 Paris" }
|
||||
};
|
||||
await SiteRepository.UpsertAsync(new LocalSite
|
||||
{
|
||||
Id = siteInterventionId,
|
||||
ClientId = clientId,
|
||||
PayloadJson = Serialize(siteDto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
await InterventionRepository.UpsertAsync(CreateIntervention(interventionId, "INT-002", clientId, siteInterventionId));
|
||||
await RendezVousRepository.UpsertAsync(CreateRendezVous(
|
||||
rdvId, jour.AddHours(9), jour.AddHours(10),
|
||||
interventionId: interventionId.ToString()));
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : nom et adresse proviennent du site de l'intervention.
|
||||
AgendaItem item = Assert.Single(result);
|
||||
Assert.Equal("Chantier Intervention", item.SiteNom);
|
||||
Assert.Equal("12 rue du Test, 75000 Paris", item.SiteAdresse);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ChampsDenormalises_DuRdv_SontUtilises()
|
||||
{
|
||||
// Arrange : un rendez-vous portant déjà les champs dénormalisés résolus
|
||||
// par la jointure SQL serveur (RDV → Intervention → Client/Site/Adresse).
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
var dto = new RendezVousDetailsDto
|
||||
{
|
||||
Id = rdvId.ToString(),
|
||||
Titre = "RDV enrichi",
|
||||
Debut = jour.AddHours(9),
|
||||
Fin = jour.AddHours(10),
|
||||
InterventionReference = "INT-REF",
|
||||
InterventionNumeroDevis = "DEV-99",
|
||||
InterventionReferenceExterne = "EXT-88",
|
||||
ClientNom = "Client Joint",
|
||||
SiteNom = "Site Joint",
|
||||
SiteAdresse = "12 rue Joint, LYON, 69001"
|
||||
};
|
||||
await RendezVousRepository.UpsertAsync(new LocalRendezVous
|
||||
{
|
||||
Id = rdvId,
|
||||
PayloadJson = Serialize(dto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : les champs dénormalisés du rendez-vous sont utilisés directement.
|
||||
AgendaItem item = Assert.Single(result);
|
||||
Assert.Equal("INT-REF", item.InterventionReference);
|
||||
Assert.Equal("DEV-99", item.InterventionNumeroDevis);
|
||||
Assert.Equal("EXT-88", item.InterventionReferenceExterne);
|
||||
Assert.Equal("Client Joint", item.ClientNom);
|
||||
Assert.Equal("Site Joint", item.SiteNom);
|
||||
Assert.Equal("12 rue Joint, LYON, 69001", item.SiteAdresse);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RendezVousAnnule_EstAnnuleVrai()
|
||||
{
|
||||
// Arrange : statut « Annulé ».
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
await RendezVousRepository.UpsertAsync(
|
||||
CreateRendezVous(Guid.NewGuid(), jour.AddHours(9), jour.AddHours(10), statut: "Annulé", estAnnule: true));
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert
|
||||
Assert.True(Assert.Single(result).EstAnnule);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RendezVousStatutConfirmeAvecAnnulation_EstAnnuleFaux()
|
||||
{
|
||||
// Arrange : champs d'annulation présents mais statut remis à « Confirmé ».
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
await RendezVousRepository.UpsertAsync(
|
||||
CreateRendezVous(Guid.NewGuid(), jour.AddHours(9), jour.AddHours(10), statut: "Confirmé", estAnnule: true));
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : le statut fait foi — le rendez-vous n'est plus annulé.
|
||||
Assert.False(Assert.Single(result).EstAnnule);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRendezVousById_RetourneLeBonRendezVous()
|
||||
{
|
||||
// Arrange
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
await RendezVousRepository.UpsertAsync(CreateRendezVous(rdvId, jour.AddHours(9), jour.AddHours(10), titre: "Détail"));
|
||||
|
||||
// Act
|
||||
AgendaItem? item = await _service.GetRendezVousByIdAsync(rdvId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(item);
|
||||
Assert.Equal(rdvId, item.RendezVousGuid);
|
||||
Assert.Equal("Détail", item.Titre);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRendezVousById_Introuvable_RetourneNull()
|
||||
{
|
||||
// Act
|
||||
AgendaItem? item = await _service.GetRendezVousByIdAsync(Guid.NewGuid());
|
||||
|
||||
// Assert
|
||||
Assert.Null(item);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PayloadInvalide_IgnoreLeRendezVous()
|
||||
{
|
||||
// Arrange
|
||||
await RendezVousRepository.UpsertAsync(new LocalRendezVous
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PayloadJson = "not-json",
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(DateTime.Today);
|
||||
|
||||
// Assert : le payload invalide est ignoré sans erreur.
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollaborateursAffectes_SontResolusEtExposes()
|
||||
{
|
||||
// Arrange : deux affectations, l'une portant son nom, l'autre résolue
|
||||
// depuis la table locale Collaborateur.
|
||||
Guid collaborateur1 = Guid.NewGuid();
|
||||
Guid collaborateur2 = Guid.NewGuid();
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
|
||||
var collaborateurDto = new CollaborateurDetailsDto
|
||||
{
|
||||
Id = collaborateur2.ToString(),
|
||||
NomAffiche = "Marie Local"
|
||||
};
|
||||
await CollaborateurRepository.UpsertAsync(new LocalCollaborateur
|
||||
{
|
||||
Id = collaborateur2,
|
||||
PayloadJson = Serialize(collaborateurDto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
var dto = new RendezVousDetailsDto
|
||||
{
|
||||
Id = rdvId.ToString(),
|
||||
Titre = "RDV équipe",
|
||||
Debut = jour.AddHours(9),
|
||||
Fin = jour.AddHours(10),
|
||||
Collaborateurs =
|
||||
[
|
||||
new AffectationCollaborateurDto { CollaborateurId = collaborateur1.ToString(), NomAffiche = "Jean Porté" },
|
||||
new AffectationCollaborateurDto { CollaborateurId = collaborateur2.ToString() }
|
||||
]
|
||||
};
|
||||
await RendezVousRepository.UpsertAsync(new LocalRendezVous
|
||||
{
|
||||
Id = rdvId,
|
||||
PayloadJson = Serialize(dto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : identités et noms des deux collaborateurs exposés.
|
||||
AgendaItem item = Assert.Single(result);
|
||||
Assert.Equal(
|
||||
new[] { collaborateur1, collaborateur2 }.OrderBy(g => g).ToArray(),
|
||||
item.CollaborateurGuids.OrderBy(g => g).ToArray());
|
||||
Assert.Contains("Jean Porté", item.Collaborateurs);
|
||||
Assert.Contains("Marie Local", item.Collaborateurs);
|
||||
Assert.Equal("Jean Porté", item.CollaborateurNom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CollaborateursTempsValides_SontExposesParAffectation()
|
||||
{
|
||||
// Arrange : RDV groupe avec deux affectations, une seule validée.
|
||||
Guid collaborateurValide = Guid.NewGuid();
|
||||
Guid collaborateurNonValide = Guid.NewGuid();
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
|
||||
var dto = new RendezVousDetailsDto
|
||||
{
|
||||
Id = rdvId.ToString(),
|
||||
Titre = "RDV équipe",
|
||||
Debut = jour.AddHours(9),
|
||||
Fin = jour.AddHours(10),
|
||||
Collaborateurs =
|
||||
[
|
||||
new AffectationCollaborateurDto { CollaborateurId = collaborateurValide.ToString(), TempsValide = true },
|
||||
new AffectationCollaborateurDto { CollaborateurId = collaborateurNonValide.ToString(), TempsValide = false }
|
||||
]
|
||||
};
|
||||
await RendezVousRepository.UpsertAsync(new LocalRendezVous
|
||||
{
|
||||
Id = rdvId,
|
||||
PayloadJson = Serialize(dto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : seul le collaborateur validé est exposé par affectation ; le
|
||||
// drapeau global TempsValide reste « au moins une affectation ».
|
||||
AgendaItem item = Assert.Single(result);
|
||||
Assert.True(item.TempsValide);
|
||||
Assert.Equal(new[] { collaborateurValide }, item.CollaborateursTempsValides);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRendezVousPourCollaborateurs_ChargeDepuisApi()
|
||||
{
|
||||
// Arrange
|
||||
Guid collab1 = Guid.NewGuid();
|
||||
Guid collab2 = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 8, 12);
|
||||
|
||||
var dto1 = new RendezVousDetailsDto
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Titre = "RDV 1",
|
||||
Debut = jour.AddHours(9),
|
||||
Fin = jour.AddHours(10),
|
||||
SiteNom = "Site A",
|
||||
SiteAdresse = "1 rue A",
|
||||
Collaborateurs =
|
||||
[
|
||||
new AffectationCollaborateurDto { CollaborateurId = collab1.ToString(), NomAffiche = "Alice" }
|
||||
]
|
||||
};
|
||||
var dto2 = new RendezVousDetailsDto
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Titre = "RDV 2",
|
||||
Debut = jour.AddHours(11),
|
||||
Fin = jour.AddHours(12),
|
||||
Collaborateurs =
|
||||
[
|
||||
new AffectationCollaborateurDto { CollaborateurId = collab2.ToString(), NomAffiche = "Bob" }
|
||||
]
|
||||
};
|
||||
|
||||
var api = new FakeRendezVousApiClient([(collab1, dto1), (collab2, dto2)]);
|
||||
var service = new AgendaService(
|
||||
RendezVousRepository, ClientRepository, SiteRepository, InterventionRepository, CollaborateurRepository, api, SessionService);
|
||||
|
||||
// Act : agenda du collaborateur 1 uniquement.
|
||||
IReadOnlyList<AgendaItem> result =
|
||||
await service.GetRendezVousForCollaborateursAsync([collab1], jour, jour.AddDays(7));
|
||||
|
||||
// Assert : seul le rendez-vous du collaborateur ciblé est retourné et enrichi.
|
||||
AgendaItem item = Assert.Single(result);
|
||||
Assert.Equal("RDV 1", item.Titre);
|
||||
Assert.Equal("Site A", item.SiteNom);
|
||||
Assert.Equal("1 rue A", item.SiteAdresse);
|
||||
Assert.Equal(collab1, item.CollaborateurGuids.Single());
|
||||
}
|
||||
|
||||
// ===== Nom du collaborateur affecté (annuaire du bootstrap) =====
|
||||
|
||||
/// <summary>
|
||||
/// Rendez-vous local dont l'affectation ne porte AUCUN nom et dont le
|
||||
/// collaborateur n'existe pas dans la table locale : c'est l'état RÉEL d'un
|
||||
/// appareil mobile, le snapshot ne transportant jamais les collaborateurs en
|
||||
/// bloc (voir <c>MobileSnapshotService</c>).
|
||||
/// </summary>
|
||||
private static LocalRendezVous RendezVousSansNomDeCollaborateur(
|
||||
Guid rdvId,
|
||||
DateTime debut,
|
||||
DateTime fin,
|
||||
Guid collaborateurGuid)
|
||||
{
|
||||
var dto = new RendezVousDetailsDto
|
||||
{
|
||||
Id = rdvId.ToString("D"),
|
||||
Titre = "RDV sans nom de collaborateur",
|
||||
Debut = debut,
|
||||
Fin = fin,
|
||||
Statut = new ReferenceValueDto { Label = "Planifié" },
|
||||
Collaborateurs =
|
||||
[
|
||||
new AffectationCollaborateurDto { CollaborateurId = collaborateurGuid.ToString("D") }
|
||||
]
|
||||
};
|
||||
|
||||
return new LocalRendezVous
|
||||
{
|
||||
Id = rdvId,
|
||||
PayloadJson = Serialize(dto),
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRendezVousForDay_NomAbsentDuSnapshot_EstResoluParLAnnuaireDuBootstrap()
|
||||
{
|
||||
// Arrange : l'annuaire local (bootstrap) connaît le collaborateur.
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
Guid collaborateurGuid = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 3, 10, 9, 0, 0);
|
||||
await RendezVousRepository.UpsertAsync(
|
||||
RendezVousSansNomDeCollaborateur(rdvId, jour, jour.AddHours(1), collaborateurGuid));
|
||||
SessionService.Bootstrap = new LocalBootstrapState
|
||||
{
|
||||
Collaborateurs =
|
||||
[
|
||||
new CollaborateurSummaryDto
|
||||
{
|
||||
Id = collaborateurGuid.ToString("D"),
|
||||
NomAffiche = "Jean DURAND"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : le nom est résolu SANS appel réseau et sans table locale.
|
||||
AgendaItem item = Assert.Single(result);
|
||||
Assert.Equal("Jean DURAND", item.CollaborateurNom);
|
||||
Assert.Equal("Jean DURAND", Assert.Single(item.Collaborateurs));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRendezVousById_NomAbsentDuSnapshot_EstResoluParLAnnuaireDuBootstrap()
|
||||
{
|
||||
// La fiche rendez-vous emprunte un autre chemin (rafraîchissement serveur
|
||||
// puis repli sur le snapshot local) : le nom doit y être résolu de la même
|
||||
// façon, sinon la fiche affiche un collaborateur vide.
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
Guid collaborateurGuid = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 3, 10, 14, 0, 0);
|
||||
await RendezVousRepository.UpsertAsync(
|
||||
RendezVousSansNomDeCollaborateur(rdvId, jour, jour.AddHours(2), collaborateurGuid));
|
||||
SessionService.Bootstrap = new LocalBootstrapState
|
||||
{
|
||||
Collaborateurs =
|
||||
[
|
||||
new CollaborateurSummaryDto
|
||||
{
|
||||
Id = collaborateurGuid.ToString("D"),
|
||||
NomAffiche = "Marie MARTIN"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
AgendaItem? item = await _service.GetRendezVousByIdAsync(rdvId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(item);
|
||||
Assert.Equal("Marie MARTIN", item!.CollaborateurNom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRendezVousForDay_NomPorteParUneEquipeDuBootstrap_EstUtilise()
|
||||
{
|
||||
// L'annuaire plat peut être vide alors que les équipes portent les noms.
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
Guid collaborateurGuid = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 3, 11, 8, 0, 0);
|
||||
await RendezVousRepository.UpsertAsync(
|
||||
RendezVousSansNomDeCollaborateur(rdvId, jour, jour.AddHours(1), collaborateurGuid));
|
||||
SessionService.Bootstrap = new LocalBootstrapState
|
||||
{
|
||||
Equipes =
|
||||
[
|
||||
new EquipeDto
|
||||
{
|
||||
Collaborateurs =
|
||||
[
|
||||
new CollaborateurSummaryDto
|
||||
{
|
||||
Id = collaborateurGuid.ToString("D"),
|
||||
NomAffiche = "Paul PETIT"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Paul PETIT", Assert.Single(result).CollaborateurNom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRendezVousForDay_NomIntrouvable_ResteVideSansNomInvente()
|
||||
{
|
||||
// Arrange : ni affectation nommée, ni table locale, ni annuaire.
|
||||
Guid rdvId = Guid.NewGuid();
|
||||
DateTime jour = new(2026, 3, 12, 9, 0, 0);
|
||||
await RendezVousRepository.UpsertAsync(
|
||||
RendezVousSansNomDeCollaborateur(rdvId, jour, jour.AddHours(1), Guid.NewGuid()));
|
||||
SessionService.Bootstrap = null;
|
||||
|
||||
// Act
|
||||
IReadOnlyList<AgendaItem> result = await _service.GetRendezVousForDayAsync(jour);
|
||||
|
||||
// Assert : aucun identifiant technique n'est affiché à la place d'un nom.
|
||||
AgendaItem item = Assert.Single(result);
|
||||
Assert.Null(item.CollaborateurNom);
|
||||
Assert.Empty(item.Collaborateurs);
|
||||
Assert.Equal(rdvId, item.RendezVousGuid);
|
||||
}
|
||||
|
||||
private sealed class FakeRendezVousApiClient : StubMobileApiClient
|
||||
{
|
||||
private readonly IReadOnlyList<(Guid CollaborateurGuid, RendezVousDetailsDto Dto)> _rdvs;
|
||||
|
||||
public FakeRendezVousApiClient(IReadOnlyList<(Guid CollaborateurGuid, RendezVousDetailsDto Dto)> rdvs)
|
||||
{
|
||||
_rdvs = rdvs;
|
||||
}
|
||||
|
||||
public override Task<IReadOnlyList<RendezVousDetailsDto>> GetRendezVousByCollaborateurAsync(
|
||||
Guid collaborateurGuid,
|
||||
DateTime debutMin,
|
||||
DateTime debutMax,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult<IReadOnlyList<RendezVousDetailsDto>>(
|
||||
_rdvs.Where(r => r.CollaborateurGuid == collaborateurGuid).Select(r => r.Dto).ToList());
|
||||
}
|
||||
|
||||
public override Task<IReadOnlyList<RendezVousDetailsDto>> GetRendezVousByCollaborateursAsync(
|
||||
IReadOnlyList<Guid> collaborateurGuids,
|
||||
DateTime debutMin,
|
||||
DateTime debutMax,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var set = collaborateurGuids.ToHashSet();
|
||||
return Task.FromResult<IReadOnlyList<RendezVousDetailsDto>>(
|
||||
_rdvs.Where(r => set.Contains(r.CollaborateurGuid)).Select(r => r.Dto).ToList());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user