Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,946 @@
|
||||
using System.Reflection;
|
||||
using Omika.Core.Contracts.Common;
|
||||
using Omika.Core.Contracts.Configuration;
|
||||
using Omika.Core.Contracts.Mobile.Sync;
|
||||
using Omika.Core.Contracts.Organisation;
|
||||
using Omika.Core.Contracts.Planning;
|
||||
using Omika.Core.Domain.Commands;
|
||||
using Omika.Core.Domain.Entities.Organisation;
|
||||
using Omika.Core.Domain.ValueObjects;
|
||||
using Omika.Core.Interfaces;
|
||||
using Omika.Core.Interfaces.Mobile;
|
||||
using Omika.Core.Services;
|
||||
|
||||
using Omika.Core.Domain;
|
||||
|
||||
namespace Omika.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires de <see cref="CollaborateurService"/> avec un fake
|
||||
/// <see cref="ICollaborateurRepository"/> en mémoire. Aucune base réelle.
|
||||
/// </summary>
|
||||
public class CollaborateurServiceTests
|
||||
{
|
||||
// =========================================================================
|
||||
// SEARCH
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_CallsRepositoryOnce()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest());
|
||||
|
||||
Assert.Equal(1, repo.SearchCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_TransmitsPage()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest { Page = 3 });
|
||||
|
||||
Assert.Equal(3, repo.LastSearchCriteria?.Page);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_TransmitsPageSize()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest { PageSize = 50 });
|
||||
|
||||
Assert.Equal(50, repo.LastSearchCriteria?.PageSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_TransmitsSearch()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest { Search = "test" });
|
||||
|
||||
Assert.Equal("test", repo.LastSearchCriteria?.Search);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_TransmitsEquipeId()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest { EquipeId = "equipe-1" });
|
||||
|
||||
Assert.Equal("equipe-1", repo.LastSearchCriteria?.EquipeId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_TransmitsActif()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest { Actif = true });
|
||||
|
||||
Assert.True(repo.LastSearchCriteria?.Actif);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_TransmitsEstArchive()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest { EstArchive = true });
|
||||
|
||||
Assert.True(repo.LastSearchCriteria?.EstArchive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_TransmitsSortBy()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest { SortBy = "Nom" });
|
||||
|
||||
Assert.Equal("Nom", repo.LastSearchCriteria?.SortBy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_TransmitsSortDescending()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest { SortDescending = true });
|
||||
|
||||
Assert.True(repo.LastSearchCriteria?.SortDescending);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_TransmitsCancellationToken()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
await service.SearchAsync(new SearchCollaborateurRequest(), cts.Token);
|
||||
|
||||
Assert.Equal(cts.Token, repo.LastCancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_EmptyResult_ReturnsEmptyPage()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
SearchResult = new PagedResult<Collaborateur>([], 1, 20, 0, 0)
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
PagedResult<CollaborateurSummaryDto> result = await service.SearchAsync(
|
||||
new SearchCollaborateurRequest());
|
||||
|
||||
Assert.Empty(result.Items);
|
||||
Assert.Equal(0, result.TotalCount);
|
||||
Assert.Equal(0, result.TotalPages);
|
||||
Assert.Equal(1, result.Page);
|
||||
Assert.Equal(20, result.PageSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_SingleCollaborateur_MapsToSummaryDto()
|
||||
{
|
||||
var collaborateur = new Collaborateur
|
||||
{
|
||||
Nom = "Dupont",
|
||||
Prenom = "Jean",
|
||||
Code = "JD",
|
||||
Coordonnees = new ContactDetails { Email = "jean@example.com", Phone = "0102030405" },
|
||||
Actif = true
|
||||
};
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
SearchResult = new PagedResult<Collaborateur>([collaborateur], 1, 20, 1, 1)
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
PagedResult<CollaborateurSummaryDto> result = await service.SearchAsync(
|
||||
new SearchCollaborateurRequest());
|
||||
|
||||
CollaborateurSummaryDto dto = Assert.Single(result.Items);
|
||||
Assert.Equal("Jean Dupont", dto.NomAffiche);
|
||||
Assert.Equal("JD", dto.Code);
|
||||
Assert.Equal("jean@example.com", dto.Email);
|
||||
Assert.Equal("0102030405", dto.Telephone);
|
||||
Assert.True(dto.Actif);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_MultipleCollaborateurs_MapsAllToDtos()
|
||||
{
|
||||
var collaborateurList = new[]
|
||||
{
|
||||
new Collaborateur { Nom = "Alpha" },
|
||||
new Collaborateur { Nom = "Beta" },
|
||||
new Collaborateur { Nom = "Gamma" }
|
||||
};
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
SearchResult = new PagedResult<Collaborateur>(collaborateurList, 1, 20, 3, 1)
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
PagedResult<CollaborateurSummaryDto> result = await service.SearchAsync(
|
||||
new SearchCollaborateurRequest());
|
||||
|
||||
Assert.Equal(3, result.Items.Count);
|
||||
Assert.Equal(3, result.TotalCount);
|
||||
Assert.Equal("Alpha", result.Items[0].NomAffiche);
|
||||
Assert.Equal("Beta", result.Items[1].NomAffiche);
|
||||
Assert.Equal("Gamma", result.Items[2].NomAffiche);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_PreservesTotalCount()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
SearchResult = new PagedResult<Collaborateur>([], 2, 10, 42, 5)
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
PagedResult<CollaborateurSummaryDto> result = await service.SearchAsync(
|
||||
new SearchCollaborateurRequest());
|
||||
|
||||
Assert.Equal(42, result.TotalCount);
|
||||
Assert.Equal(2, result.Page);
|
||||
Assert.Equal(10, result.PageSize);
|
||||
Assert.Equal(5, result.TotalPages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_IdComesFromCollaborateurContractMapper()
|
||||
{
|
||||
var collaborateur = new Collaborateur { Nom = "Test" };
|
||||
var expectedId = collaborateur.Id.ToString();
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
SearchResult = new PagedResult<Collaborateur>([collaborateur], 1, 20, 1, 1)
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
PagedResult<CollaborateurSummaryDto> result = await service.SearchAsync(
|
||||
new SearchCollaborateurRequest());
|
||||
|
||||
Assert.Equal(expectedId, result.Items[0].Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SearchAsync_ServiceDoesNotRePaginate()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
SearchResult = new PagedResult<Collaborateur>(
|
||||
[new Collaborateur { Nom = "A" }, new Collaborateur { Nom = "B" }], 3, 15, 100, 7)
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
PagedResult<CollaborateurSummaryDto> result = await service.SearchAsync(
|
||||
new SearchCollaborateurRequest { Page = 1, PageSize = 5 });
|
||||
|
||||
Assert.Equal(2, result.Items.Count);
|
||||
Assert.Equal(100, result.TotalCount);
|
||||
Assert.Equal(7, result.TotalPages);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// DETAIL
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_TransmitsExactId()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.GetByIdAsync("test-id-123");
|
||||
|
||||
Assert.Equal("test-id-123", repo.LastDetailsId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_TransmitsCancellationToken()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
await service.GetByIdAsync("42", cts.Token);
|
||||
|
||||
Assert.Equal(cts.Token, repo.LastCancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_RepositoryReturnsNull_ReturnsNull()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository { DetailsResult = null };
|
||||
var service = CreateService(repo);
|
||||
|
||||
CollaborateurDetailsDto? result = await service.GetByIdAsync("999");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_MapsToDetailsDto()
|
||||
{
|
||||
var collaborateur = new Collaborateur
|
||||
{
|
||||
Nom = "Dupont",
|
||||
Prenom = "Jean",
|
||||
Code = "JD",
|
||||
Civilite = new ReferenceValue("M.") { Key = "M" },
|
||||
Statut = new ReferenceValue("Actif") { Key = "ACTIF" },
|
||||
Coordonnees = new ContactDetails { Email = "jean@example.com" }
|
||||
};
|
||||
var repo = new FakeCollaborateurRepository { DetailsResult = collaborateur };
|
||||
var service = CreateService(repo);
|
||||
|
||||
CollaborateurDetailsDto? result = await service.GetByIdAsync("42");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("Dupont", result.Nom);
|
||||
Assert.Equal("Jean", result.Prenom);
|
||||
Assert.Equal("JD", result.Code);
|
||||
Assert.Equal("M.", result.Civilite?.Label);
|
||||
Assert.Equal("Actif", result.Statut?.Label);
|
||||
Assert.Equal("jean@example.com", result.Coordonnees?.Email);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_IdComesFromMapper()
|
||||
{
|
||||
var collaborateur = new Collaborateur { Nom = "Test" };
|
||||
var expectedId = collaborateur.Id.ToString();
|
||||
var repo = new FakeCollaborateurRepository { DetailsResult = collaborateur };
|
||||
var service = CreateService(repo);
|
||||
|
||||
CollaborateurDetailsDto? result = await service.GetByIdAsync("42");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(expectedId, result.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_NoGuidParsingInService()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository();
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.GetByIdAsync("not-a-guid-nor-int");
|
||||
|
||||
Assert.Equal("not-a-guid-nor-int", repo.LastDetailsId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetByIdAsync_RepositoryException_Propagated()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
ThrowOnDetails = new InvalidOperationException("Infrastructure error")
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => service.GetByIdAsync("42"));
|
||||
|
||||
Assert.Equal("Infrastructure error", ex.Message);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// CREATE
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_CallsRepositoryOnce()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
CreateResult = new Collaborateur { Nom = "Nouveau" }
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.CreateAsync(new CreateCollaborateurRequest { Nom = "Nouveau" });
|
||||
|
||||
Assert.Equal(1, repo.CreateCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_MapsAllProperties()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
CreateResult = new Collaborateur { Nom = "Nouveau" }
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.CreateAsync(new CreateCollaborateurRequest
|
||||
{
|
||||
Code = "JD",
|
||||
Nom = "Dupont",
|
||||
Prenom = "Jean",
|
||||
CiviliteKey = "M",
|
||||
StatutKey = "ACTIF",
|
||||
Email = "jean@example.com",
|
||||
Telephone = "0102030405",
|
||||
Mobile = "0607080910",
|
||||
Fonction = "Développeur",
|
||||
SocieteId = "societe-1",
|
||||
CouleurPlanning = "#FF0000",
|
||||
Actif = true
|
||||
});
|
||||
|
||||
Assert.Equal("JD", repo.LastCreateCommand!.Code);
|
||||
Assert.Equal("Dupont", repo.LastCreateCommand!.Nom);
|
||||
Assert.Equal("Jean", repo.LastCreateCommand!.Prenom);
|
||||
Assert.Equal("M", repo.LastCreateCommand!.CiviliteKey);
|
||||
Assert.Equal("ACTIF", repo.LastCreateCommand!.StatutKey);
|
||||
Assert.Equal("jean@example.com", repo.LastCreateCommand!.Email);
|
||||
Assert.Equal("0102030405", repo.LastCreateCommand!.Telephone);
|
||||
Assert.Equal("0607080910", repo.LastCreateCommand!.Mobile);
|
||||
Assert.Equal("Développeur", repo.LastCreateCommand!.Fonction);
|
||||
Assert.Equal("societe-1", repo.LastCreateCommand!.SocieteId);
|
||||
Assert.Equal("#FF0000", repo.LastCreateCommand!.CouleurPlanning);
|
||||
Assert.True(repo.LastCreateCommand!.Actif);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_ResultMappedToDetails()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
CreateResult = new Collaborateur { Nom = "Nouveau" }
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
CollaborateurDetailsDto result = await service.CreateAsync(new CreateCollaborateurRequest { Nom = "Nouveau" });
|
||||
|
||||
Assert.Equal("Nouveau", result.Nom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_CancellationTokenTransmitted()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
CreateResult = new Collaborateur { Nom = "Nouveau" }
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
await service.CreateAsync(new CreateCollaborateurRequest { Nom = "Nouveau" }, cts.Token);
|
||||
|
||||
Assert.Equal(cts.Token, repo.LastCreateCancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_ExceptionPropagated()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
ThrowOnCreate = new InvalidOperationException("Panne")
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => service.CreateAsync(new CreateCollaborateurRequest { Nom = "Nouveau" }));
|
||||
|
||||
Assert.Equal("Panne", ex.Message);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// UPDATE
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_MapsAllProperties()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
UpdateResult = new Collaborateur { Nom = "Updated" }
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.UpdateAsync("42", new UpdateCollaborateurRequest
|
||||
{
|
||||
Code = "JD",
|
||||
Nom = "Updated",
|
||||
CiviliteKey = "M",
|
||||
StatutKey = "ACTIF"
|
||||
});
|
||||
|
||||
Assert.Equal(1, repo.UpdateCallCount);
|
||||
Assert.Equal("42", repo.LastUpdateId);
|
||||
Assert.Equal("JD", repo.LastUpdateCommand!.Code);
|
||||
Assert.Equal("Updated", repo.LastUpdateCommand!.Nom);
|
||||
Assert.Equal("M", repo.LastUpdateCommand!.CiviliteKey);
|
||||
Assert.Equal("ACTIF", repo.LastUpdateCommand!.StatutKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_NullsPreserved()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository { UpdateResult = new Collaborateur { Nom = "X" } };
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.UpdateAsync("42", new UpdateCollaborateurRequest());
|
||||
|
||||
Assert.Null(repo.LastUpdateCommand!.CiviliteKey);
|
||||
Assert.Null(repo.LastUpdateCommand!.StatutKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_RepoReturnsNull_ThrowsKeyNotFound()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository { UpdateResult = null };
|
||||
var service = CreateService(repo);
|
||||
|
||||
await Assert.ThrowsAsync<KeyNotFoundException>(
|
||||
() => service.UpdateAsync("42", new UpdateCollaborateurRequest()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_IdTransmittedExactly()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
UpdateResult = new Collaborateur { Nom = "XY" }
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.UpdateAsync("my-guid-123", new UpdateCollaborateurRequest());
|
||||
|
||||
Assert.Equal("my-guid-123", repo.LastUpdateId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_CancellationTokenTransmitted()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository { UpdateResult = new Collaborateur { Nom = "X" } };
|
||||
var service = CreateService(repo);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
await service.UpdateAsync("42", new UpdateCollaborateurRequest(), cts.Token);
|
||||
|
||||
Assert.Equal(cts.Token, repo.LastUpdateCancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateAsync_ExceptionPropagated()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
ThrowOnUpdate = new InvalidOperationException("Panne")
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => service.UpdateAsync("42", new UpdateCollaborateurRequest()));
|
||||
|
||||
Assert.Equal("Panne", ex.Message);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ARCHIVE
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveAsync_TruePropagated()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository { ArchiveResult = true };
|
||||
var service = CreateService(repo);
|
||||
|
||||
bool result = await service.ArchiveAsync("42");
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(1, repo.ArchiveCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveAsync_False_ReturnsFalse()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository { ArchiveResult = false };
|
||||
var service = CreateService(repo);
|
||||
|
||||
bool result = await service.ArchiveAsync("42");
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveAsync_IdTransmittedExactly()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository { ArchiveResult = true };
|
||||
var service = CreateService(repo);
|
||||
|
||||
await service.ArchiveAsync("my-guid-123");
|
||||
|
||||
Assert.Equal("my-guid-123", repo.LastArchiveId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveAsync_CancellationTokenTransmitted()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository { ArchiveResult = true };
|
||||
var service = CreateService(repo);
|
||||
using var cts = new CancellationTokenSource();
|
||||
|
||||
await service.ArchiveAsync("42", cts.Token);
|
||||
|
||||
Assert.Equal(cts.Token, repo.LastArchiveCancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ArchiveAsync_ExceptionPropagated()
|
||||
{
|
||||
var repo = new FakeCollaborateurRepository
|
||||
{
|
||||
ThrowOnArchive = new InvalidOperationException("Panne")
|
||||
};
|
||||
var service = CreateService(repo);
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => service.ArchiveAsync("42"));
|
||||
|
||||
Assert.Equal("Panne", ex.Message);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// ARCHITECTURE
|
||||
// =========================================================================
|
||||
|
||||
[Fact]
|
||||
public void Service_ImplementsICollaborateurService()
|
||||
{
|
||||
Assert.True(typeof(ICollaborateurService).IsAssignableFrom(typeof(CollaborateurService)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Service_DependsOnICollaborateurRepository()
|
||||
{
|
||||
var type = typeof(CollaborateurService);
|
||||
bool hasRepoParam = type.GetConstructors()
|
||||
.Any(c => c.GetParameters()
|
||||
.Any(p => p.ParameterType == typeof(ICollaborateurRepository)));
|
||||
|
||||
Assert.True(hasRepoParam, "CollaborateurService doit dépendre de ICollaborateurRepository.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Service_HasNoOmikaDataReferences()
|
||||
{
|
||||
var type = typeof(CollaborateurService);
|
||||
var refs = GetReferencedTypes(type);
|
||||
|
||||
Assert.DoesNotContain(refs, t =>
|
||||
t.Namespace is not null && t.Namespace.StartsWith("Omika.Data"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Service_HasNoBatikReferences()
|
||||
{
|
||||
var type = typeof(CollaborateurService);
|
||||
var refs = GetReferencedTypes(type);
|
||||
|
||||
Assert.DoesNotContain(refs, t =>
|
||||
t.Name.Contains("Batik", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Service_HasNoHfsqlReferences()
|
||||
{
|
||||
var type = typeof(CollaborateurService);
|
||||
var refs = GetReferencedTypes(type);
|
||||
|
||||
Assert.DoesNotContain(refs, t =>
|
||||
t.Name.Contains("Row", StringComparison.OrdinalIgnoreCase)
|
||||
&& t.Namespace is not null
|
||||
&& (t.Namespace.Contains("HfSql", StringComparison.OrdinalIgnoreCase)
|
||||
|| t.Namespace.Contains("Models.Omika", StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Service_HasNoSqlStatements()
|
||||
{
|
||||
string source = ReadSourceFile(typeof(CollaborateurService));
|
||||
|
||||
Assert.DoesNotContain("SELECT ", source, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("FROM ", source, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Service_HasNoApplicationRuntimeContext()
|
||||
{
|
||||
var type = typeof(CollaborateurService);
|
||||
var refs = GetReferencedTypes(type);
|
||||
|
||||
Assert.DoesNotContain(refs, t =>
|
||||
t.Name.Contains("ApplicationRuntimeContext", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Service_HasNoDatabaseSchemaVersion()
|
||||
{
|
||||
var type = typeof(CollaborateurService);
|
||||
var refs = GetReferencedTypes(type);
|
||||
|
||||
Assert.DoesNotContain(refs, t =>
|
||||
t.Name.Contains("DatabaseSchemaVersion", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Service_HasNoOdbcReferences()
|
||||
{
|
||||
var type = typeof(CollaborateurService);
|
||||
var refs = GetReferencedTypes(type);
|
||||
|
||||
Assert.DoesNotContain(refs, t =>
|
||||
t.Namespace is not null && t.Namespace.StartsWith("System.Data.Odbc", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// HELPERS
|
||||
// =========================================================================
|
||||
|
||||
private static CollaborateurService CreateService(FakeCollaborateurRepository repo)
|
||||
=> new CollaborateurService(
|
||||
repo,
|
||||
new FakeIdentityResolver(),
|
||||
new FakeRendezVousService(),
|
||||
new FakeParametreMetierRepository());
|
||||
|
||||
private sealed class FakeRendezVousService : IRendezVousService
|
||||
{
|
||||
public Task<PagedResult<RendezVousSummaryDto>> SearchAsync(SearchRendezVousRequest request, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new PagedResult<RendezVousSummaryDto>([], 1, 20, 0, 0));
|
||||
|
||||
public Task<PagedResult<RendezVousDetailsDto>> SearchDetailsAsync(SearchRendezVousRequest request, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new PagedResult<RendezVousDetailsDto>([], 1, 20, 0, 0));
|
||||
|
||||
public Task<RendezVousDetailsDto?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<RendezVousDetailsDto?>(null);
|
||||
|
||||
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)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task ArchiveAsync(string id, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task<bool> SetTempsValideAsync(string rendezVousId, string collaborateurId, bool tempsValide, CancellationToken cancellationToken = default, AuditActor? auteur = null)
|
||||
=> Task.FromResult(true);
|
||||
}
|
||||
|
||||
private sealed class FakeParametreMetierRepository : IParametreMetierRepository
|
||||
{
|
||||
public IReadOnlyList<ParametreMetierDto> Items { get; set; } = [];
|
||||
|
||||
public Task<IReadOnlyList<ParametreMetierDto>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(Items);
|
||||
}
|
||||
|
||||
private sealed class FakeIdentityResolver : IMobileEntityIdentityResolver
|
||||
{
|
||||
public Guid CreateCanonicalIdentity(string entityType, PhysicalEntityIdentity physical)
|
||||
=> physical.Guid ?? Guid.Empty;
|
||||
|
||||
public PhysicalEntityIdentity? ResolvePhysicalIdentity(string entityType, Guid publicGuid)
|
||||
=> null;
|
||||
|
||||
public bool IsCanonical(string entityType, Guid publicGuid)
|
||||
=> publicGuid != Guid.Empty;
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetReferencedTypes(Type type)
|
||||
{
|
||||
var members = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
|
||||
.Select(m => m.DeclaringType)
|
||||
.Where(t => t is not null)
|
||||
.Cast<Type>();
|
||||
|
||||
var parameters = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.SelectMany(m => m.GetParameters())
|
||||
.Select(p => p.ParameterType);
|
||||
|
||||
var returnTypes = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.Select(m => m.ReturnType);
|
||||
|
||||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.Select(p => p.PropertyType);
|
||||
|
||||
var ctorParams = type.GetConstructors()
|
||||
.SelectMany(c => c.GetParameters())
|
||||
.Select(p => p.ParameterType);
|
||||
|
||||
return members.Concat(parameters).Concat(returnTypes).Concat(properties).Concat(ctorParams);
|
||||
}
|
||||
|
||||
private static string ReadSourceFile(Type type)
|
||||
{
|
||||
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
|
||||
var dir = new DirectoryInfo(baseDir);
|
||||
while (dir != null && !File.Exists(Path.Combine(dir.FullName, "Omika.slnx")))
|
||||
dir = dir.Parent;
|
||||
|
||||
string root = dir?.FullName ?? baseDir;
|
||||
return File.ReadAllText(Path.Combine(root, "src", "Omika.Core", "Services", type.Name + ".cs"));
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// FAKE REPOSITORY
|
||||
// =========================================================================
|
||||
|
||||
private sealed class FakeCollaborateurRepository : ICollaborateurRepository
|
||||
{
|
||||
public int SearchCallCount { get; private set; }
|
||||
public int DetailsCallCount { get; private set; }
|
||||
public int UpdateCallCount { get; private set; }
|
||||
public int ArchiveCallCount { get; private set; }
|
||||
public int CreateCallCount { get; private set; }
|
||||
public CollaborateurSearchCriteria? LastSearchCriteria { get; private set; }
|
||||
public string? LastDetailsId { get; private set; }
|
||||
public string? LastUpdateId { get; private set; }
|
||||
public string? LastArchiveId { get; private set; }
|
||||
public CollaborateurUpdateCommand? LastUpdateCommand { get; private set; }
|
||||
public CollaborateurCreateCommand? LastCreateCommand { get; private set; }
|
||||
public CancellationToken LastUpdateCancellationToken { get; private set; }
|
||||
public CancellationToken LastArchiveCancellationToken { get; private set; }
|
||||
public CancellationToken LastCreateCancellationToken { get; private set; }
|
||||
public CancellationToken LastCancellationToken { get; private set; }
|
||||
public PagedResult<Collaborateur>? SearchResult { get; set; }
|
||||
public Collaborateur? DetailsResult { get; set; }
|
||||
public Collaborateur? UpdateResult { get; set; }
|
||||
public Collaborateur? CreateResult { get; set; }
|
||||
public bool ArchiveResult { get; set; }
|
||||
public Exception? ThrowOnSearch { get; set; }
|
||||
public Exception? ThrowOnDetails { get; set; }
|
||||
public Exception? ThrowOnUpdate { get; set; }
|
||||
public Exception? ThrowOnArchive { get; set; }
|
||||
public Exception? ThrowOnCreate { get; set; }
|
||||
|
||||
public Task<Collaborateur> CreateAsync(
|
||||
CollaborateurCreateCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
CreateCallCount++;
|
||||
LastCreateCommand = command;
|
||||
LastCreateCancellationToken = cancellationToken;
|
||||
|
||||
if (ThrowOnCreate is not null)
|
||||
throw ThrowOnCreate;
|
||||
|
||||
return Task.FromResult(CreateResult ?? new Collaborateur { Nom = command.Nom });
|
||||
}
|
||||
|
||||
public Task<PagedResult<Collaborateur>> SearchAsync(
|
||||
CollaborateurSearchCriteria criteria,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
SearchCallCount++;
|
||||
LastSearchCriteria = criteria;
|
||||
LastCancellationToken = cancellationToken;
|
||||
|
||||
if (ThrowOnSearch is not null)
|
||||
throw ThrowOnSearch;
|
||||
|
||||
return Task.FromResult(SearchResult
|
||||
?? new PagedResult<Collaborateur>([], 1, 20, 0, 0));
|
||||
}
|
||||
|
||||
public Task<PagedResult<Collaborateur>> SearchDetailsAsync(
|
||||
CollaborateurSearchCriteria criteria,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
SearchCallCount++;
|
||||
LastSearchCriteria = criteria;
|
||||
LastCancellationToken = cancellationToken;
|
||||
|
||||
if (ThrowOnSearch is not null)
|
||||
throw ThrowOnSearch;
|
||||
|
||||
return Task.FromResult(SearchResult
|
||||
?? new PagedResult<Collaborateur>([], 1, 20, 0, 0));
|
||||
}
|
||||
|
||||
public Task<Collaborateur?> GetDetailsAsync(
|
||||
|
||||
string id,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
DetailsCallCount++;
|
||||
LastDetailsId = id;
|
||||
LastCancellationToken = cancellationToken;
|
||||
|
||||
if (ThrowOnDetails is not null)
|
||||
throw ThrowOnDetails;
|
||||
|
||||
return Task.FromResult(DetailsResult);
|
||||
}
|
||||
|
||||
public Task<CollaborateurUtilisateur?> GetUtilisateurByCollaborateurAsync(
|
||||
string collaborateurId,
|
||||
CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<CollaborateurUtilisateur?>(null);
|
||||
|
||||
public Task<string?> GetParametreSystemeValueAsync(string code, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<string?>(null);
|
||||
|
||||
public Task<Collaborateur?> UpdateAsync(
|
||||
string id, CollaborateurUpdateCommand command,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
UpdateCallCount++;
|
||||
LastUpdateId = id;
|
||||
LastUpdateCommand = command;
|
||||
LastUpdateCancellationToken = cancellationToken;
|
||||
|
||||
if (ThrowOnUpdate is not null)
|
||||
throw ThrowOnUpdate;
|
||||
|
||||
return Task.FromResult(UpdateResult);
|
||||
}
|
||||
|
||||
public Task<bool> ArchiveAsync(
|
||||
string id,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArchiveCallCount++;
|
||||
LastArchiveId = id;
|
||||
LastArchiveCancellationToken = cancellationToken;
|
||||
|
||||
if (ThrowOnArchive is not null)
|
||||
throw ThrowOnArchive;
|
||||
|
||||
return Task.FromResult(ArchiveResult);
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<long>> GetSocieteIdsAsync(long collaborateurId, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<IReadOnlyList<long>>([]);
|
||||
|
||||
public Task<IReadOnlyList<long>> GetAllowedCollaborateurIdsAsync(long collaborateurId, bool includeSocietePrincipale, bool includeSocietes, bool includeEquipe, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<IReadOnlyList<long>>([]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user