205 lines
9.7 KiB
C#
205 lines
9.7 KiB
C#
using Omika.Core.Compatibility;
|
|
using Omika.Core.Contracts.Common;
|
|
using Omika.Core.Contracts.Mobile.Sync;
|
|
using Omika.Core.Contracts.Organisation;
|
|
using Omika.Core.Contracts.Planning;
|
|
using Omika.Core.Contracts.Sites;
|
|
using Omika.Core.Domain;
|
|
using Omika.Core.Domain.Commands;
|
|
using Omika.Core.Domain.Entities.Clients;
|
|
using Omika.Core.Interfaces;
|
|
using Omika.Core.Interfaces.Mobile;
|
|
using Omika.Core.Services;
|
|
using Omika.Core.Services.Mobile;
|
|
|
|
namespace Omika.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// Tests du SiteService : les relations du site (contacts, clients, rendez-vous)
|
|
/// doivent être résolues par LI_PKID numérique, jamais par le GUID public Omika.
|
|
/// </summary>
|
|
public class SiteServiceTests
|
|
{
|
|
private sealed class FakeSiteRepository : ISiteRepository
|
|
{
|
|
public long SiteNumericId { get; set; } = 42;
|
|
public string? LastContactsId { get; private set; }
|
|
public string? LastClientsId { get; private set; }
|
|
|
|
public Task<Site> CreateAsync(SiteCreateCommand command, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<PagedResult<Site>> SearchAsync(SiteSearchCriteria criteria, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<PagedResult<Site>> SearchDetailsAsync(SiteSearchCriteria criteria, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<Site?> GetDetailsAsync(string id, CancellationToken cancellationToken = default)
|
|
{
|
|
var site = new Site();
|
|
site.SourceReference = new LegacyEntityReference(DatabaseSchemaVersion.Omika, NumericId: SiteNumericId);
|
|
return Task.FromResult<Site?>(site);
|
|
}
|
|
|
|
public Task<IReadOnlyList<Contact>> GetContactsAsync(string id, CancellationToken cancellationToken = default)
|
|
{
|
|
LastContactsId = id;
|
|
return Task.FromResult<IReadOnlyList<Contact>>([]);
|
|
}
|
|
|
|
public Task<IReadOnlyList<Client>> GetClientsAsync(string id, CancellationToken cancellationToken = default)
|
|
{
|
|
LastClientsId = id;
|
|
return Task.FromResult<IReadOnlyList<Client>>([]);
|
|
}
|
|
|
|
public Task<Site?> UpdateAsync(string id, SiteUpdateCommand command, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
|
|
private sealed class FakeRendezVousService : IRendezVousService
|
|
{
|
|
public IReadOnlyList<RendezVousDetailsDto> DetailsResults { get; set; } = [];
|
|
|
|
public Task<PagedResult<RendezVousSummaryDto>> SearchAsync(SearchRendezVousRequest request, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<PagedResult<RendezVousDetailsDto>> SearchDetailsAsync(SearchRendezVousRequest request, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(new PagedResult<RendezVousDetailsDto>(
|
|
DetailsResults, 1, DetailsResults.Count, DetailsResults.Count, DetailsResults.Count == 0 ? 0 : 1));
|
|
|
|
public Task<RendezVousDetailsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<RendezVousDetailsDto> CreateAsync(CreateRendezVousRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<RendezVousDetailsDto> UpdateAsync(string id, UpdateRendezVousRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task CancelAsync(string id, AnnulationRendezVousDto annulation, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task ArchiveAsync(string id, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<bool> SetTempsValideAsync(string rendezVousId, string collaborateurId, bool tempsValide, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
|
|
private sealed class FakeCollaborateurService : ICollaborateurService
|
|
{
|
|
public Task<PagedResult<CollaborateurSummaryDto>> SearchAsync(SearchCollaborateurRequest request, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<PagedResult<CollaborateurDetailsDto>> SearchDetailsAsync(SearchCollaborateurRequest request, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<CollaborateurDetailsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<CollaborateurDetailsDto> CreateAsync(CreateCollaborateurRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<CollaborateurDetailsDto> UpdateAsync(string id, UpdateCollaborateurRequest request, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<IReadOnlyList<long>> GetSocieteIdsAsync(long collaborateurId, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<IReadOnlyList<long>> GetAllowedCollaborateurIdsAsync(long collaborateurId, bool includeSocietePrincipale, bool includeSocietes, bool includeEquipe, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<IReadOnlyList<RendezVousDetailsDto>> GetRendezVousAsync(string collaborateurId, DateTime debutMin, DateTime debutMax, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
|
|
public Task<IReadOnlyList<RendezVousDetailsDto>> GetRendezVousForCollaborateursAsync(IReadOnlyList<string> collaborateurIds, DateTime debutMin, DateTime debutMax, CancellationToken cancellationToken = default)
|
|
=> throw new NotSupportedException();
|
|
}
|
|
|
|
private sealed class FakeIdentityResolver : IMobileEntityIdentityResolver
|
|
{
|
|
public Guid CreateCanonicalIdentity(string entityType, PhysicalEntityIdentity physical)
|
|
=> physical.Guid ?? Guid.NewGuid();
|
|
|
|
public PhysicalEntityIdentity? ResolvePhysicalIdentity(string entityType, Guid publicGuid)
|
|
=> new PhysicalEntityIdentity { Guid = publicGuid };
|
|
|
|
public bool IsCanonical(string entityType, Guid publicGuid) => publicGuid != Guid.Empty;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetContactsAsync_GuidPublic_ResoutLiPkidAvantLaRequete()
|
|
{
|
|
// Arrange : schéma Omika — le resolver retourne un GUID SANS NumericId.
|
|
var repo = new FakeSiteRepository { SiteNumericId = 42 };
|
|
var service = new SiteService(
|
|
repo, new FakeRendezVousService(), new FakeCollaborateurService(), new FakeIdentityResolver());
|
|
|
|
// Act : l'identité publique du site est un GUID (comme côté mobile Omika).
|
|
await service.GetContactsAsync(Guid.NewGuid().ToString("D"));
|
|
|
|
// Assert : le repository a reçu le PK numérique (42), jamais le GUID.
|
|
Assert.Equal("42", repo.LastContactsId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetClientsAsync_GuidPublic_ResoutLiPkidAvantLaRequete()
|
|
{
|
|
var repo = new FakeSiteRepository { SiteNumericId = 7 };
|
|
var service = new SiteService(
|
|
repo, new FakeRendezVousService(), new FakeCollaborateurService(), new FakeIdentityResolver());
|
|
|
|
await service.GetClientsAsync(Guid.NewGuid().ToString("D"));
|
|
|
|
Assert.Equal("7", repo.LastClientsId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetRendezVousAsync_RdvDateSurAffectation_RestitueLeRdvRabattu()
|
|
{
|
|
// Omika : RV_Date (racine) vide → Debut = default. La fiche lieu doit
|
|
// rabattre la date de l'affectation (RVC_DateDeb) AVANT l'éligibilité,
|
|
// comme la fiche intervention et le snapshot. Sans cela, un rendez-vous
|
|
// réellement daté est écarté et l'onglet « Rendez-vous » reste vide.
|
|
DateTime debutAffectation = new DateTime(2026, 8, 19, 10, 0, 0);
|
|
var rdvService = new FakeRendezVousService
|
|
{
|
|
DetailsResults =
|
|
[
|
|
new RendezVousDetailsDto
|
|
{
|
|
Id = Guid.NewGuid().ToString(),
|
|
Debut = default,
|
|
Collaborateurs =
|
|
[
|
|
new AffectationCollaborateurDto
|
|
{
|
|
PeriodeDebut = debutAffectation,
|
|
PeriodeFin = debutAffectation.AddHours(1.5)
|
|
}
|
|
]
|
|
}
|
|
]
|
|
};
|
|
var service = new SiteService(
|
|
new FakeSiteRepository { SiteNumericId = 42 },
|
|
rdvService,
|
|
new FakeCollaborateurService(),
|
|
new FakeIdentityResolver());
|
|
|
|
IReadOnlyList<RendezVousDetailsDto> result =
|
|
await service.GetRendezVousAsync(Guid.NewGuid().ToString("D"));
|
|
|
|
RendezVousDetailsDto item = Assert.Single(result);
|
|
Assert.Equal(debutAffectation, item.Debut);
|
|
}
|
|
}
|