460 lines
16 KiB
C#
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);
|
|
}
|
|
}
|