419 lines
14 KiB
C#
419 lines
14 KiB
C#
using System.Text.Json;
|
|
using Omika.App.Local.Models;
|
|
using Omika.App.Local.Infrastructure;
|
|
using Omika.App.Local.Services;
|
|
using Omika.Core.Contracts.Common;
|
|
using Omika.Core.Contracts.Interventions;
|
|
using Omika.Core.Contracts.Organisation;
|
|
using Omika.Core.Contracts.Planning;
|
|
|
|
namespace Omika.Tests.Mobile.Local;
|
|
|
|
/// <summary>
|
|
/// Tests du service de lecture des collaborateurs depuis les données locales
|
|
/// SQLite. Relations réellement démontrables localement :
|
|
/// <list type="bullet">
|
|
/// <item>Collaborateur → RendezVous : <c>RendezVousDetailsDto.Collaborateurs</c>
|
|
/// contient une affectation dont <c>CollaborateurId</c> correspond au GUID public.</item>
|
|
/// <item>Collaborateur → Interventions : <c>InterventionDetailsDto.CollaborateurIds</c>
|
|
/// contient le GUID public du collaborateur.</item>
|
|
/// </list>
|
|
/// Aucune relation n'est déduite par transitivité ni par commodité. Lecture
|
|
/// seule : aucune écriture ni Outbox n'est créée.
|
|
/// </summary>
|
|
public class CollaborateurLocalServiceTests : LocalTestBase
|
|
{
|
|
private readonly CollaborateurLocalService _service;
|
|
|
|
public CollaborateurLocalServiceTests()
|
|
{
|
|
_service = new CollaborateurLocalService(CollaborateurRepository, RendezVousRepository, ContextInterventionCacheRepository, ApiClient, SessionService);
|
|
}
|
|
|
|
private static string Serialize<T>(T value) => JsonSerializer.Serialize(value);
|
|
|
|
private static LocalCollaborateur CreateCollaborateur(
|
|
Guid id,
|
|
string? nom = null,
|
|
string? prenom = null,
|
|
string? nomAffiche = null,
|
|
string? code = null,
|
|
string? statut = null,
|
|
string? telephone = null,
|
|
string? mobile = null,
|
|
string? email = null,
|
|
bool actif = true,
|
|
bool estArchive = false)
|
|
{
|
|
var dto = new CollaborateurDetailsDto
|
|
{
|
|
Id = id.ToString(),
|
|
Nom = nom,
|
|
Prenom = prenom,
|
|
NomAffiche = nomAffiche,
|
|
Code = code,
|
|
Statut = statut is null ? null : new ReferenceValueDto { Label = statut },
|
|
Coordonnees = telephone is null && mobile is null && email is null
|
|
? null
|
|
: new ContactDetailsDto { Phone = telephone, Mobile = mobile, Email = email },
|
|
Actif = actif,
|
|
EstArchive = estArchive
|
|
};
|
|
return new LocalCollaborateur
|
|
{
|
|
Id = id,
|
|
PayloadJson = Serialize(dto),
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
};
|
|
}
|
|
|
|
private static LocalRendezVous CreateRendezVous(
|
|
Guid id,
|
|
Guid collaborateurId,
|
|
DateTime debut,
|
|
DateTime fin,
|
|
string titre = "Rendez-vous",
|
|
string? statut = "Planifié")
|
|
{
|
|
var dto = new RendezVousDetailsDto
|
|
{
|
|
Id = id.ToString(),
|
|
Titre = titre,
|
|
Debut = debut,
|
|
Fin = fin,
|
|
Statut = statut is null ? null : new ReferenceValueDto { Label = statut },
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto { CollaborateurId = collaborateurId.ToString() }
|
|
]
|
|
};
|
|
return new LocalRendezVous
|
|
{
|
|
Id = id,
|
|
PayloadJson = Serialize(dto),
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
};
|
|
}
|
|
|
|
private static LocalRendezVous CreateRendezVousSansCollaborateur(
|
|
Guid id,
|
|
DateTime debut,
|
|
DateTime fin,
|
|
string titre = "Rendez-vous")
|
|
{
|
|
var dto = new RendezVousDetailsDto
|
|
{
|
|
Id = id.ToString(),
|
|
Titre = titre,
|
|
Debut = debut,
|
|
Fin = fin,
|
|
Collaborateurs = []
|
|
};
|
|
return new LocalRendezVous
|
|
{
|
|
Id = id,
|
|
PayloadJson = Serialize(dto),
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
};
|
|
}
|
|
|
|
private static LocalIntervention CreateIntervention(
|
|
Guid id,
|
|
Guid collaborateurId,
|
|
string reference,
|
|
string titre,
|
|
DateTime? datePrevueDebut = null,
|
|
string? etat = null)
|
|
{
|
|
var dto = new InterventionDetailsDto
|
|
{
|
|
Id = id.ToString(),
|
|
Reference = reference,
|
|
Titre = titre,
|
|
DatePrevueDebut = datePrevueDebut,
|
|
Etat = etat is null ? null : new ReferenceValueDto { Label = etat },
|
|
CollaborateurIds = [collaborateurId.ToString()]
|
|
};
|
|
return new LocalIntervention
|
|
{
|
|
Id = id,
|
|
PayloadJson = Serialize(dto),
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
};
|
|
}
|
|
|
|
private static LocalIntervention CreateInterventionSansCollaborateur(
|
|
Guid id,
|
|
string reference,
|
|
string titre)
|
|
{
|
|
var dto = new InterventionDetailsDto
|
|
{
|
|
Id = id.ToString(),
|
|
Reference = reference,
|
|
Titre = titre,
|
|
CollaborateurIds = []
|
|
};
|
|
return new LocalIntervention
|
|
{
|
|
Id = id,
|
|
PayloadJson = Serialize(dto),
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
};
|
|
}
|
|
|
|
private async Task CacheCollaborateurInterventionsAsync(Guid collaborateurId, params LocalIntervention[] interventions)
|
|
{
|
|
var details = interventions
|
|
.Select(i => JsonSerializer.Deserialize<InterventionDetailsDto>(i.PayloadJson)!)
|
|
.ToList();
|
|
await ContextInterventionCacheRepository.ReplaceAsync(
|
|
InterventionContextTypes.Collaborateur,
|
|
collaborateurId,
|
|
details);
|
|
}
|
|
|
|
// ===================== LISTE =====================
|
|
|
|
[Fact]
|
|
public async Task AucunCollaborateur_RetourneListeVide()
|
|
{
|
|
// Act
|
|
var result = await _service.GetCollaborateursAsync();
|
|
|
|
// Assert
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UnCollaborateur_RetourneLeCollaborateur()
|
|
{
|
|
// Arrange
|
|
Guid collaborateurId = Guid.NewGuid();
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(collaborateurId, "Dupont", "Jean", code: "C001"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateursAsync();
|
|
|
|
// Assert
|
|
var item = Assert.Single(result);
|
|
Assert.Equal(collaborateurId, item.CollaborateurGuid);
|
|
Assert.Equal("Jean Dupont", item.DisplayName);
|
|
Assert.Equal("C001", item.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PlusieursCollaborateurs_RetourneTous()
|
|
{
|
|
// Arrange
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Dupont", "Jean"));
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Martin", "Paul"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateursAsync();
|
|
|
|
// Assert
|
|
Assert.Equal(2, result.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Tri_DeterministeParNomAffichePuisCode()
|
|
{
|
|
// Arrange
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Beta", "B", code: "C002"));
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Alpha", "A", code: "C001"));
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Alpha", "A", code: "C003"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateursAsync();
|
|
|
|
// Assert : tri stable par nom affiché (insensible à la casse) puis code.
|
|
Assert.Equal("A Alpha", result[0].DisplayName);
|
|
Assert.Equal("C001", result[0].Code);
|
|
Assert.Equal("A Alpha", result[1].DisplayName);
|
|
Assert.Equal("C003", result[1].Code);
|
|
Assert.Equal("B Beta", result[2].DisplayName);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RechercheLocale_InsensibleALaCasse()
|
|
{
|
|
// Arrange
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Dupont", "Jean", code: "C001"));
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Martin", "Paul", code: "C002"));
|
|
|
|
// Act : le service retourne tout ; la recherche est gérée par le ViewModel.
|
|
var result = await _service.GetCollaborateursAsync();
|
|
|
|
// Assert
|
|
Assert.Equal(2, result.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RechercheSansResultat_ListeVide()
|
|
{
|
|
// Arrange
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Dupont", "Jean"));
|
|
|
|
// Act : le service retourne tout ; la recherche est gérée par le ViewModel.
|
|
var result = await _service.GetCollaborateursAsync();
|
|
|
|
// Assert
|
|
Assert.Single(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task PayloadInvalide_EntreeIgnoree()
|
|
{
|
|
// Arrange
|
|
await CollaborateurRepository.UpsertAsync(new LocalCollaborateur
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
PayloadJson = "not-json",
|
|
UpdatedAtUtc = DateTimeOffset.UtcNow
|
|
});
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Dupont", "Jean"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateursAsync();
|
|
|
|
// Assert : l'entrée invalide est ignorée proprement.
|
|
var item = Assert.Single(result);
|
|
Assert.Equal("Jean Dupont", item.DisplayName);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task IdentitePublique_Conservee()
|
|
{
|
|
// Arrange
|
|
Guid collaborateurId = Guid.NewGuid();
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(collaborateurId, "Dupont", "Jean"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateursAsync();
|
|
|
|
// Assert : l'identité publique (GUID) est conservée.
|
|
var item = Assert.Single(result);
|
|
Assert.Equal(collaborateurId, item.CollaborateurGuid);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FonctionneOffline_SansAppelApi()
|
|
{
|
|
// Arrange : aucune dépendance réseau, uniquement SQLite local.
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(Guid.NewGuid(), "Dupont", "Jean"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateursAsync();
|
|
|
|
// Assert
|
|
Assert.Single(result);
|
|
}
|
|
|
|
// ===================== DÉTAIL =====================
|
|
|
|
[Fact]
|
|
public async Task Detail_CollaborateurTrouve()
|
|
{
|
|
// Arrange
|
|
Guid collaborateurId = Guid.NewGuid();
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(collaborateurId, "Dupont", "Jean", code: "C001"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateurByIdAsync(collaborateurId);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal(collaborateurId, result.CollaborateurGuid);
|
|
Assert.Equal("Jean Dupont", result.NomAffiche);
|
|
Assert.Equal("C001", result.Code);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Detail_CollaborateurInconnu_RetourneNull()
|
|
{
|
|
// Act
|
|
var result = await _service.GetCollaborateurByIdAsync(Guid.NewGuid());
|
|
|
|
// Assert
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Detail_DonneesReellesChargees()
|
|
{
|
|
// Arrange
|
|
Guid collaborateurId = Guid.NewGuid();
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(
|
|
collaborateurId, "Dupont", "Jean", code: "C001", statut: "Actif",
|
|
telephone: "0102030405", mobile: "0607080910", email: "jean@omika.fr"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateurByIdAsync(collaborateurId);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal("Dupont", result.Nom);
|
|
Assert.Equal("Jean", result.Prenom);
|
|
Assert.Equal("C001", result.Code);
|
|
Assert.Equal("Actif", result.Statut);
|
|
Assert.Equal("0102030405", result.Phone);
|
|
Assert.Equal("0607080910", result.Mobile);
|
|
Assert.Equal("jean@omika.fr", result.Email);
|
|
Assert.True(result.HasPhone);
|
|
Assert.True(result.HasEmail);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Detail_ChampsOptionnelsAbsents()
|
|
{
|
|
// Arrange : collaborateur minimal sans coordonnées ni statut.
|
|
Guid collaborateurId = Guid.NewGuid();
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(collaborateurId, "Dupont", "Jean"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateurByIdAsync(collaborateurId);
|
|
|
|
// Assert : champs optionnels absents restent nuls sans erreur.
|
|
Assert.NotNull(result);
|
|
Assert.Null(result.Phone);
|
|
Assert.Null(result.Mobile);
|
|
Assert.Null(result.Email);
|
|
Assert.Null(result.Statut);
|
|
Assert.False(result.HasPhone);
|
|
Assert.False(result.HasEmail);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Detail_IdentitePublique_Conservee()
|
|
{
|
|
// Arrange
|
|
Guid collaborateurId = Guid.NewGuid();
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(collaborateurId, "Dupont", "Jean"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateurByIdAsync(collaborateurId);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal(collaborateurId, result.CollaborateurGuid);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Detail_AucuneRelation_NeProvoquePasDErreur()
|
|
{
|
|
// Arrange : collaborateur sans rendez-vous ni interventions.
|
|
Guid collaborateurId = Guid.NewGuid();
|
|
await CollaborateurRepository.UpsertAsync(CreateCollaborateur(collaborateurId, "Dupont", "Jean"));
|
|
|
|
// Act
|
|
var result = await _service.GetCollaborateurByIdAsync(collaborateurId);
|
|
|
|
// Assert : collections vides sans erreur.
|
|
Assert.NotNull(result);
|
|
Assert.Empty(result.RendezVous);
|
|
Assert.Empty(result.Interventions);
|
|
}
|
|
|
|
}
|