Files
Omika/tests/Omika.Tests/Mobile/Local/InterventionContactsServiceTests.cs
T
2026-10-23 18:46:50 +02:00

460 lines
16 KiB
C#

using System.Text.Json;
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;
namespace Omika.Tests.Mobile.Local;
/// <summary>
/// Tests du service Contacts d'une intervention.
/// La relation utilisée est la liste explicite <c>ContactIds</c> du DTO
/// d'intervention, résolue vers les Personnes/Contacts stockées localement.
/// Aucun contact n'est déduit du Client : seule la relation explicite
/// Intervention → Contact est utilisée.
/// </summary>
public class InterventionContactsServiceTests : LocalTestBase
{
private readonly InterventionContactsService _service;
public InterventionContactsServiceTests()
{
_service = new InterventionContactsService(
InterventionRepository, PersonneRepository, InterventionCacheRepository,
ContextInterventionCacheRepository, ApiClient);
}
private static string Serialize<T>(T value) => JsonSerializer.Serialize(value);
private static LocalIntervention CreateIntervention(Guid id, params Guid[] contactIds)
{
var dto = new InterventionDetailsDto
{
Id = id.ToString(),
Reference = "INT-001",
Titre = "Intervention",
ContactIds = contactIds.Select(c => c.ToString()).ToList()
};
return new LocalIntervention
{
Id = id,
PayloadJson = Serialize(dto),
UpdatedAtUtc = DateTimeOffset.UtcNow
};
}
private static LocalPersonne CreatePersonne(
Guid id,
string nom,
string prenom,
string? email = null,
string? phone = null,
string? mobile = null,
Guid? clientId = null)
{
var dto = new ContactDto
{
Id = id.ToString(),
Nom = nom,
Prenom = prenom,
NomAffiche = $"{prenom} {nom}",
ClientId = clientId?.ToString(),
Coordonnees = new ContactDetailsDto { Email = email, Phone = phone, Mobile = mobile },
Actif = true
};
return new LocalPersonne
{
Id = id,
ClientId = clientId,
PayloadJson = Serialize(dto),
UpdatedAtUtc = DateTimeOffset.UtcNow
};
}
[Fact]
public async Task InterventionA_RetourneUniquementSesContacts()
{
// Arrange
Guid interventionA = Guid.NewGuid();
Guid interventionB = Guid.NewGuid();
Guid contactA = Guid.NewGuid();
Guid contactB = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(interventionA, contactA));
await InterventionRepository.UpsertAsync(CreateIntervention(interventionB, contactB));
await PersonneRepository.UpsertAsync(CreatePersonne(contactA, "Dupont", "Alice"));
await PersonneRepository.UpsertAsync(CreatePersonne(contactB, "Martin", "Bob"));
// Act
var resultA = await _service.GetContactsForInterventionAsync(interventionA);
// Assert : A ne retourne que son contact.
var contact = Assert.Single(resultA);
Assert.Equal(contactA, contact.ContactGuid);
Assert.Equal("Alice Dupont", contact.DisplayName);
}
[Fact]
public async Task InterventionB_NeRetournePasLesContactsDeA()
{
// Arrange
Guid interventionA = Guid.NewGuid();
Guid interventionB = Guid.NewGuid();
Guid contactA = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(interventionA, contactA));
await InterventionRepository.UpsertAsync(CreateIntervention(interventionB));
await PersonneRepository.UpsertAsync(CreatePersonne(contactA, "Dupont", "Alice"));
// Act
var resultB = await _service.GetContactsForInterventionAsync(interventionB);
// Assert : B n'a aucun contact (le contact de A n'est pas inclus).
Assert.Empty(resultB);
}
[Fact]
public async Task ContactSansRelation_NonInclus()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid contactSansRelation = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention));
await PersonneRepository.UpsertAsync(CreatePersonne(contactSansRelation, "Durand", "Claire"));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert : le contact sans relation n'est pas inclus.
Assert.Empty(result);
}
[Fact]
public async Task AucuneAssociationBaseeSurLeClient()
{
// Arrange
Guid clientId = Guid.NewGuid();
Guid intervention = Guid.NewGuid();
Guid contactDuClient = Guid.NewGuid();
// L'intervention n'a AUCUN ContactIds, mais un contact du même client existe.
await InterventionRepository.UpsertAsync(CreateIntervention(intervention));
await PersonneRepository.UpsertAsync(CreatePersonne(contactDuClient, "Durand", "Claire", clientId: clientId));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert : aucun contact n'est déduit du Client.
Assert.Empty(result);
}
[Fact]
public async Task GuidPublicContact_Conserve()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid contact = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention, contact));
await PersonneRepository.UpsertAsync(CreatePersonne(contact, "Dupont", "Alice"));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert : l'identité publique du contact est conservée.
var item = Assert.Single(result);
Assert.Equal(contact, item.ContactGuid);
}
[Fact]
public async Task InterventionAvecUnContact_RetourneLeContact()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid contact = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention, contact));
await PersonneRepository.UpsertAsync(CreatePersonne(contact, "Dupont", "Alice", email: "alice@test.fr", mobile: "0600000000"));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert
var item = Assert.Single(result);
Assert.Equal("Alice Dupont", item.DisplayName);
Assert.Equal("alice@test.fr", item.Email);
Assert.Equal("0600000000", item.Mobile);
Assert.True(item.HasPhone);
Assert.True(item.HasEmail);
}
[Fact]
public async Task PlusieursContacts_TriesParNom()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid zoe = Guid.NewGuid();
Guid alice = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention, zoe, alice));
await PersonneRepository.UpsertAsync(CreatePersonne(zoe, "Zoe", "Zoe"));
await PersonneRepository.UpsertAsync(CreatePersonne(alice, "Alice", "Alice"));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert : tri déterministe par nom affiché.
Assert.Equal(2, result.Count);
Assert.Equal("Alice Alice", result[0].DisplayName);
Assert.Equal("Zoe Zoe", result[1].DisplayName);
}
[Fact]
public async Task AucunContact_RetourneListeVide()
{
// Arrange
Guid intervention = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert
Assert.Empty(result);
}
[Fact]
public async Task ContactIncomplet_RetourneNomConstruit()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid contact = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention, contact));
await PersonneRepository.UpsertAsync(CreatePersonne(contact, "Dupont", "Alice"));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert : nom construit à partir du prénom + nom.
var item = Assert.Single(result);
Assert.Equal("Alice Dupont", item.DisplayName);
}
[Fact]
public async Task TelephoneAbsent_IndiquePasDeTelephone()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid contact = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention, contact));
await PersonneRepository.UpsertAsync(CreatePersonne(contact, "Dupont", "Alice", email: "alice@test.fr"));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert
var item = Assert.Single(result);
Assert.False(item.HasPhone);
Assert.True(item.HasEmail);
}
[Fact]
public async Task EmailAbsent_IndiquePasDEmail()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid contact = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention, contact));
await PersonneRepository.UpsertAsync(CreatePersonne(contact, "Dupont", "Alice", mobile: "0600000000"));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert
var item = Assert.Single(result);
Assert.True(item.HasPhone);
Assert.False(item.HasEmail);
}
[Fact]
public async Task RelationInvalide_IgnoreeProprement()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid contact = Guid.NewGuid();
// ContactIds contient un GUID invalide + un GUID valide.
var dto = new InterventionDetailsDto
{
Id = intervention.ToString(),
Reference = "INT-001",
ContactIds = ["not-a-guid", contact.ToString()]
};
await InterventionRepository.UpsertAsync(new LocalIntervention
{
Id = intervention,
PayloadJson = Serialize(dto),
UpdatedAtUtc = DateTimeOffset.UtcNow
});
await PersonneRepository.UpsertAsync(CreatePersonne(contact, "Dupont", "Alice"));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert : l'entrée invalide est ignorée, le contact valide est retourné.
var item = Assert.Single(result);
Assert.Equal(contact, item.ContactGuid);
}
[Fact]
public async Task InterventionInconnue_RetourneListeVide()
{
// Act
var result = await _service.GetContactsForInterventionAsync(Guid.NewGuid());
// Assert
Assert.Empty(result);
}
[Fact]
public async Task FonctionneOffline_SansAppelApi()
{
// Arrange : aucune dépendance réseau, uniquement SQLite local.
Guid intervention = Guid.NewGuid();
Guid contact = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention, contact));
await PersonneRepository.UpsertAsync(CreatePersonne(contact, "Dupont", "Alice"));
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert : le service fonctionne entièrement depuis SQLite.
Assert.Single(result);
}
[Fact]
public async Task GetContactById_RetourneLeContact()
{
// Arrange
Guid contact = Guid.NewGuid();
await PersonneRepository.UpsertAsync(CreatePersonne(contact, "Dupont", "Alice", email: "alice@test.fr"));
// Act
var result = await _service.GetContactByIdAsync(contact);
// Assert
Assert.NotNull(result);
Assert.Equal(contact, result.ContactGuid);
Assert.Equal("Alice Dupont", result.DisplayName);
Assert.Equal("alice@test.fr", result.Email);
}
[Fact]
public async Task GetContactById_Inconnu_RetourneNull()
{
// Act
var result = await _service.GetContactByIdAsync(Guid.NewGuid());
// Assert
Assert.Null(result);
}
// =====================================================================
// Destinataires de courriel : uniquement les contacts de l'intervention
// =====================================================================
/// <summary>
/// Le choix d'un destinataire ne propose QUE les contacts de CETTE
/// intervention, jamais le reste du répertoire local (qui contenait les
/// contacts de toutes les autres interventions et de tous les clients).
/// </summary>
[Fact]
public async Task ContactsDisponibles_UniquementLesContactsDeLIntervention()
{
// Arrange : l'intervention A a Alice ; le répertoire local contient en plus
// les contacts de l'intervention B et un contact sans aucune relation.
Guid interventionA = Guid.NewGuid();
Guid interventionB = Guid.NewGuid();
Guid alice = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(interventionA, alice));
await InterventionRepository.UpsertAsync(CreateIntervention(interventionB, Guid.NewGuid()));
await PersonneRepository.UpsertAsync(CreatePersonne(alice, "Dupont", "Alice", email: "alice@test.fr"));
await PersonneRepository.UpsertAsync(CreatePersonne(Guid.NewGuid(), "Martin", "Bob", email: "bob@test.fr"));
await PersonneRepository.UpsertAsync(CreatePersonne(Guid.NewGuid(), "Durand", "Claire", email: "claire@test.fr"));
// Act
var result = await _service.GetContactsDisponiblesAsync(interventionA);
// Assert : une seule proposition, celle de l'intervention.
var contact = Assert.Single(result);
Assert.Equal(alice, contact.ContactGuid);
Assert.Equal("alice@test.fr", contact.Email);
}
/// <summary>Un contact de l'intervention sans adresse email n'est pas proposé comme destinataire.</summary>
[Fact]
public async Task ContactsDisponibles_ExcluentLesContactsSansEmail()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid avecEmail = Guid.NewGuid();
Guid sansEmail = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention, avecEmail, sansEmail));
await PersonneRepository.UpsertAsync(CreatePersonne(avecEmail, "Dupont", "Alice", email: "alice@test.fr"));
await PersonneRepository.UpsertAsync(CreatePersonne(sansEmail, "Martin", "Bob", mobile: "0600000000"));
// Act
var result = await _service.GetContactsDisponiblesAsync(intervention);
// Assert
var contact = Assert.Single(result);
Assert.Equal(avecEmail, contact.ContactGuid);
}
/// <summary>Intervention sans contact : aucune proposition (jamais un repli sur le répertoire).</summary>
[Fact]
public async Task ContactsDisponibles_InterventionSansContact_ListeVide()
{
// Arrange : le répertoire local est rempli, l'intervention n'a aucun contact.
Guid intervention = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention));
await PersonneRepository.UpsertAsync(CreatePersonne(Guid.NewGuid(), "Dupont", "Alice", email: "alice@test.fr"));
// Act
var result = await _service.GetContactsDisponiblesAsync(intervention);
// Assert
Assert.Empty(result);
}
[Fact]
public async Task PayloadInvalide_EntreeIgnoree()
{
// Arrange
Guid intervention = Guid.NewGuid();
Guid contact = Guid.NewGuid();
await InterventionRepository.UpsertAsync(CreateIntervention(intervention, contact));
await PersonneRepository.UpsertAsync(new LocalPersonne
{
Id = contact,
PayloadJson = "not-json",
UpdatedAtUtc = DateTimeOffset.UtcNow
});
// Act
var result = await _service.GetContactsForInterventionAsync(intervention);
// Assert : l'entrée au payload invalide est ignorée proprement.
Assert.Empty(result);
}
}