Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,418 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user