650 lines
24 KiB
C#
650 lines
24 KiB
C#
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());
|
|
}
|
|
}
|
|
}
|