1042 lines
36 KiB
C#
1042 lines
36 KiB
C#
using System.Reflection;
|
|
using Omika.Core.Contracts.Common;
|
|
using Omika.Core.Contracts.Planning;
|
|
using Omika.Core.Domain;
|
|
using Omika.Core.Domain.Commands;
|
|
using Omika.Core.Domain.Entities.Planning;
|
|
using Omika.Core.Domain.ValueObjects;
|
|
using Omika.Core.Interfaces;
|
|
using Omika.Core.Services;
|
|
|
|
namespace Omika.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// Tests unitaires de <see cref="RendezVousService"/> avec un fake
|
|
/// <see cref="IRendezVousRepository"/> en mémoire. Aucune base réelle.
|
|
/// </summary>
|
|
public class RendezVousServiceTests
|
|
{
|
|
// =========================================================================
|
|
// SEARCH
|
|
// =========================================================================
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_CallsRepositoryOnce()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest());
|
|
|
|
Assert.Equal(1, repo.SearchCallCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsPage()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest { Page = 3 });
|
|
|
|
Assert.Equal(3, repo.LastSearchCriteria?.Page);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsPageSize()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest { PageSize = 50 });
|
|
|
|
Assert.Equal(50, repo.LastSearchCriteria?.PageSize);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsSearch()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest { Search = "test" });
|
|
|
|
Assert.Equal("test", repo.LastSearchCriteria?.Search);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsTypeActivite()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest { TypeActivite = "Réunion" });
|
|
|
|
Assert.Equal("Réunion", repo.LastSearchCriteria?.TypeActivite);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsStatut()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest { Statut = "Confirmé" });
|
|
|
|
Assert.Equal("Confirmé", repo.LastSearchCriteria?.Statut);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsDebutMin()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
var debutMin = new DateTime(2026, 1, 1);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest { DebutMin = debutMin });
|
|
|
|
Assert.Equal(debutMin, repo.LastSearchCriteria?.DebutMin);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsEstArchive()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest { EstArchive = true });
|
|
|
|
Assert.True(repo.LastSearchCriteria?.EstArchive);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsSortBy()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest { SortBy = "Date" });
|
|
|
|
Assert.Equal("Date", repo.LastSearchCriteria?.SortBy);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsSortDescending()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest { SortDescending = true });
|
|
|
|
Assert.True(repo.LastSearchCriteria?.SortDescending);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_TransmitsCancellationToken()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
using var cts = new CancellationTokenSource();
|
|
|
|
await service.SearchAsync(new SearchRendezVousRequest(), cts.Token);
|
|
|
|
Assert.Equal(cts.Token, repo.LastCancellationToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_EmptyResult_ReturnsEmptyPage()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
SearchResult = new PagedResult<RendezVous>([], 1, 20, 0, 0)
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
PagedResult<RendezVousSummaryDto> result = await service.SearchAsync(
|
|
new SearchRendezVousRequest());
|
|
|
|
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_SingleRendezVous_MapsToSummaryDto()
|
|
{
|
|
var rendezVous = new RendezVous
|
|
{
|
|
Titre = "Réunion",
|
|
TypeActivite = new ReferenceValue("Réunion") { Key = "REUNION" },
|
|
Statut = new ReferenceValue("Confirmé") { Key = "CONFIRME" },
|
|
Periode = new DateTimeRange(new DateTime(2026, 1, 15, 9, 0, 0), new DateTime(2026, 1, 15, 10, 0, 0))
|
|
};
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
SearchResult = new PagedResult<RendezVous>([rendezVous], 1, 20, 1, 1)
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
PagedResult<RendezVousSummaryDto> result = await service.SearchAsync(
|
|
new SearchRendezVousRequest());
|
|
|
|
RendezVousSummaryDto dto = Assert.Single(result.Items);
|
|
Assert.Equal("Réunion", dto.Titre);
|
|
Assert.Equal("Réunion", dto.TypeActivite);
|
|
Assert.Equal("Confirmé", dto.Statut);
|
|
Assert.Equal(new DateTime(2026, 1, 15, 9, 0, 0), dto.Debut);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_MultipleRendezVous_MapsAllToDtos()
|
|
{
|
|
var rendezVousList = new[]
|
|
{
|
|
new RendezVous { Titre = "Alpha" },
|
|
new RendezVous { Titre = "Beta" },
|
|
new RendezVous { Titre = "Gamma" }
|
|
};
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
SearchResult = new PagedResult<RendezVous>(rendezVousList, 1, 20, 3, 1)
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
PagedResult<RendezVousSummaryDto> result = await service.SearchAsync(
|
|
new SearchRendezVousRequest());
|
|
|
|
Assert.Equal(3, result.Items.Count);
|
|
Assert.Equal(3, result.TotalCount);
|
|
Assert.Equal("Alpha", result.Items[0].Titre);
|
|
Assert.Equal("Beta", result.Items[1].Titre);
|
|
Assert.Equal("Gamma", result.Items[2].Titre);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_PreservesTotalCount()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
SearchResult = new PagedResult<RendezVous>([], 2, 10, 42, 5)
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
PagedResult<RendezVousSummaryDto> result = await service.SearchAsync(
|
|
new SearchRendezVousRequest());
|
|
|
|
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_IdComesFromRendezVousContractMapper()
|
|
{
|
|
var rendezVous = new RendezVous { Titre = "Test" };
|
|
var expectedId = rendezVous.Id.ToString();
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
SearchResult = new PagedResult<RendezVous>([rendezVous], 1, 20, 1, 1)
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
PagedResult<RendezVousSummaryDto> result = await service.SearchAsync(
|
|
new SearchRendezVousRequest());
|
|
|
|
Assert.Equal(expectedId, result.Items[0].Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SearchAsync_ServiceDoesNotRePaginate()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
SearchResult = new PagedResult<RendezVous>(
|
|
[new RendezVous { Titre = "A" }, new RendezVous { Titre = "B" }], 3, 15, 100, 7)
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
PagedResult<RendezVousSummaryDto> result = await service.SearchAsync(
|
|
new SearchRendezVousRequest { 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 FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.GetByIdAsync("test-id-123");
|
|
|
|
Assert.Equal("test-id-123", repo.LastDetailsId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByIdAsync_TransmitsCancellationToken()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(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 FakeRendezVousRepository { DetailsResult = null };
|
|
var service = new RendezVousService(repo);
|
|
|
|
RendezVousDetailsDto? result = await service.GetByIdAsync("999");
|
|
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByIdAsync_MapsToDetailsDto()
|
|
{
|
|
var rendezVous = new RendezVous
|
|
{
|
|
Titre = "Réunion",
|
|
Description = "Description",
|
|
TypeActivite = new ReferenceValue("Réunion") { Key = "REUNION" },
|
|
Statut = new ReferenceValue("Confirmé") { Key = "CONFIRME" }
|
|
};
|
|
var repo = new FakeRendezVousRepository { DetailsResult = rendezVous };
|
|
var service = new RendezVousService(repo);
|
|
|
|
RendezVousDetailsDto? result = await service.GetByIdAsync("42");
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal("Réunion", result.Titre);
|
|
Assert.Equal("Description", result.Description);
|
|
Assert.Equal("Réunion", result.TypeActivite?.Label);
|
|
Assert.Equal("Confirmé", result.Statut?.Label);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByIdAsync_CollaborateursMapped()
|
|
{
|
|
var rendezVous = new RendezVous { Titre = "Test" };
|
|
rendezVous.AddCollaborateur(new AffectationCollaborateur { NomAffiche = "Jean Dupont" });
|
|
var repo = new FakeRendezVousRepository { DetailsResult = rendezVous };
|
|
var service = new RendezVousService(repo);
|
|
|
|
RendezVousDetailsDto? result = await service.GetByIdAsync("42");
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Single(result.Collaborateurs);
|
|
Assert.Equal("Jean Dupont", result.Collaborateurs[0].NomAffiche);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByIdAsync_ContactsMapped()
|
|
{
|
|
var rendezVous = new RendezVous { Titre = "Test" };
|
|
rendezVous.AddContact(new ParticipantRendezVous { NomAffiche = "Marie Curie" });
|
|
var repo = new FakeRendezVousRepository { DetailsResult = rendezVous };
|
|
var service = new RendezVousService(repo);
|
|
|
|
RendezVousDetailsDto? result = await service.GetByIdAsync("42");
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Single(result.Contacts);
|
|
Assert.Equal("Marie Curie", result.Contacts[0].NomAffiche);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByIdAsync_IdComesFromMapper()
|
|
{
|
|
var rendezVous = new RendezVous { Titre = "Test" };
|
|
var expectedId = rendezVous.Id.ToString();
|
|
var repo = new FakeRendezVousRepository { DetailsResult = rendezVous };
|
|
var service = new RendezVousService(repo);
|
|
|
|
RendezVousDetailsDto? result = await service.GetByIdAsync("42");
|
|
|
|
Assert.NotNull(result);
|
|
Assert.Equal(expectedId, result.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByIdAsync_NoGuidParsingInService()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(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 FakeRendezVousRepository
|
|
{
|
|
ThrowOnDetails = new InvalidOperationException("Infrastructure error")
|
|
};
|
|
var service = new RendezVousService(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 FakeRendezVousRepository
|
|
{
|
|
CreateResult = new RendezVous { Titre = "Nouveau" }
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.CreateAsync(new CreateRendezVousRequest { Titre = "Nouveau" });
|
|
|
|
Assert.Equal(1, repo.CreateCallCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_MapsAllProperties()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
CreateResult = new RendezVous { Titre = "Nouveau" }
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.CreateAsync(new CreateRendezVousRequest
|
|
{
|
|
Titre = "Nouveau",
|
|
Description = "Description",
|
|
Debut = new DateTime(2026, 1, 15, 9, 0, 0),
|
|
Fin = new DateTime(2026, 1, 15, 10, 0, 0),
|
|
TypeActivite = new ReferenceValueDto { Key = "REUNION" },
|
|
DossierId = "42",
|
|
InterventionId = "43",
|
|
SiteId = "44"
|
|
});
|
|
|
|
Assert.Equal("Nouveau", repo.LastCreateCommand!.Titre);
|
|
Assert.Equal("Description", repo.LastCreateCommand!.Description);
|
|
Assert.Equal(new DateTime(2026, 1, 15, 9, 0, 0), repo.LastCreateCommand!.Debut);
|
|
Assert.Equal(new DateTime(2026, 1, 15, 10, 0, 0), repo.LastCreateCommand!.Fin);
|
|
Assert.Equal("REUNION", repo.LastCreateCommand!.TypeActiviteKey);
|
|
Assert.Equal("42", repo.LastCreateCommand!.DossierId);
|
|
Assert.Equal("43", repo.LastCreateCommand!.InterventionId);
|
|
Assert.Equal("44", repo.LastCreateCommand!.SiteId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_ResultMappedToDetails()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
CreateResult = new RendezVous { Titre = "Nouveau" }
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
RendezVousDetailsDto result = await service.CreateAsync(new CreateRendezVousRequest { Titre = "Nouveau" });
|
|
|
|
Assert.Equal("Nouveau", result.Titre);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_CancellationTokenTransmitted()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
CreateResult = new RendezVous { Titre = "Nouveau" }
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
using var cts = new CancellationTokenSource();
|
|
|
|
await service.CreateAsync(new CreateRendezVousRequest { Titre = "Nouveau" }, cts.Token);
|
|
|
|
Assert.Equal(cts.Token, repo.LastCreateCancellationToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateAsync_ExceptionPropagated()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
ThrowOnCreate = new InvalidOperationException("Panne")
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => service.CreateAsync(new CreateRendezVousRequest { Titre = "Nouveau" }));
|
|
|
|
Assert.Equal("Panne", ex.Message);
|
|
}
|
|
|
|
// =========================================================================
|
|
// UPDATE
|
|
// =========================================================================
|
|
|
|
[Fact]
|
|
public async Task UpdateAsync_MapsAllProperties()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
UpdateResult = new RendezVous { Titre = "Updated" }
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.UpdateAsync("42", new UpdateRendezVousRequest
|
|
{
|
|
Titre = "Updated",
|
|
TypeActivite = new ReferenceValueDto { Key = "REUNION" },
|
|
Statut = new ReferenceValueDto { Key = "CONFIRME" }
|
|
});
|
|
|
|
Assert.Equal(1, repo.UpdateCallCount);
|
|
Assert.Equal("42", repo.LastUpdateId);
|
|
Assert.Equal("Updated", repo.LastUpdateCommand!.Titre);
|
|
Assert.Equal("REUNION", repo.LastUpdateCommand!.TypeActiviteKey);
|
|
Assert.Equal("CONFIRME", repo.LastUpdateCommand!.StatutKey);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateAsync_NullsPreserved()
|
|
{
|
|
var repo = new FakeRendezVousRepository { UpdateResult = new RendezVous { Titre = "X" } };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.UpdateAsync("42", new UpdateRendezVousRequest());
|
|
|
|
Assert.Null(repo.LastUpdateCommand!.TypeActiviteKey);
|
|
Assert.Null(repo.LastUpdateCommand!.StatutKey);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateAsync_RepoReturnsNull_ThrowsKeyNotFound()
|
|
{
|
|
var repo = new FakeRendezVousRepository { UpdateResult = null };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await Assert.ThrowsAsync<KeyNotFoundException>(
|
|
() => service.UpdateAsync("42", new UpdateRendezVousRequest()));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateAsync_IdTransmittedExactly()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
UpdateResult = new RendezVous { Titre = "XY" }
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.UpdateAsync("my-guid-123", new UpdateRendezVousRequest());
|
|
|
|
Assert.Equal("my-guid-123", repo.LastUpdateId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateAsync_CancellationTokenTransmitted()
|
|
{
|
|
var repo = new FakeRendezVousRepository { UpdateResult = new RendezVous { Titre = "X" } };
|
|
var service = new RendezVousService(repo);
|
|
using var cts = new CancellationTokenSource();
|
|
|
|
await service.UpdateAsync("42", new UpdateRendezVousRequest(), cts.Token);
|
|
|
|
Assert.Equal(cts.Token, repo.LastUpdateCancellationToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateAsync_ExceptionPropagated()
|
|
{
|
|
var repo = new FakeRendezVousRepository
|
|
{
|
|
ThrowOnUpdate = new InvalidOperationException("Panne")
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => service.UpdateAsync("42", new UpdateRendezVousRequest()));
|
|
|
|
Assert.Equal("Panne", ex.Message);
|
|
}
|
|
|
|
// =========================================================================
|
|
// CANCEL
|
|
// =========================================================================
|
|
|
|
[Fact]
|
|
public async Task CancelAsync_TruePropagated()
|
|
{
|
|
var repo = new FakeRendezVousRepository { CancelResult = true };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.CancelAsync("42", new AnnulationRendezVousDto());
|
|
|
|
Assert.Equal(1, repo.CancelCallCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelAsync_False_ThrowsKeyNotFound()
|
|
{
|
|
var repo = new FakeRendezVousRepository { CancelResult = false };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await Assert.ThrowsAsync<KeyNotFoundException>(
|
|
() => service.CancelAsync("42", new AnnulationRendezVousDto()));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelAsync_IdTransmittedExactly()
|
|
{
|
|
var repo = new FakeRendezVousRepository { CancelResult = true };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.CancelAsync("my-guid-123", new AnnulationRendezVousDto());
|
|
|
|
Assert.Equal("my-guid-123", repo.LastCancelId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelAsync_MapsAnnulation()
|
|
{
|
|
var repo = new FakeRendezVousRepository { CancelResult = true };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.CancelAsync("42", new AnnulationRendezVousDto
|
|
{
|
|
Origine = new ReferenceValueDto { Key = "CLIENT" },
|
|
Motif = new ReferenceValueDto { Key = "DISPONIBILITE" },
|
|
Commentaire = "Reporté",
|
|
CollaborateurId = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
|
|
});
|
|
|
|
Assert.Equal("CLIENT", repo.LastCancelCommand!.OrigineKey);
|
|
Assert.Equal("DISPONIBILITE", repo.LastCancelCommand!.MotifKey);
|
|
Assert.Equal("Reporté", repo.LastCancelCommand!.Commentaire);
|
|
Assert.Equal("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", repo.LastCancelCommand!.CollaborateurId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelAsync_TransmetLAuteurAuRepository()
|
|
{
|
|
var repo = new FakeRendezVousRepository { CancelResult = true };
|
|
var service = new RendezVousService(repo);
|
|
var auteur = new AuditActor("7", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
|
|
|
|
// L'annulation modifie le rendez-vous : l'auteur doit atteindre le
|
|
// repository pour estampiller ModifDate/ModifUser du noyau.
|
|
await service.CancelAsync("42", new AnnulationRendezVousDto(), auteur: auteur);
|
|
|
|
Assert.Same(auteur, repo.LastCancelAuteur);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CancelAsync_SansAuteur_TransmetUnAuteurVide()
|
|
{
|
|
var repo = new FakeRendezVousRepository { CancelResult = true };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.CancelAsync("42", new AnnulationRendezVousDto());
|
|
|
|
Assert.True(repo.LastCancelAuteur is { UserId: null, CollaborateurId: null });
|
|
}
|
|
|
|
// =========================================================================
|
|
// ARCHIVE
|
|
// =========================================================================
|
|
|
|
[Fact]
|
|
public async Task ArchiveAsync_TruePropagated()
|
|
{
|
|
var repo = new FakeRendezVousRepository { ArchiveResult = true };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.ArchiveAsync("42");
|
|
|
|
Assert.Equal(1, repo.ArchiveCallCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ArchiveAsync_False_ThrowsKeyNotFound()
|
|
{
|
|
var repo = new FakeRendezVousRepository { ArchiveResult = false };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await Assert.ThrowsAsync<KeyNotFoundException>(
|
|
() => service.ArchiveAsync("42"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ArchiveAsync_IdTransmittedExactly()
|
|
{
|
|
var repo = new FakeRendezVousRepository { ArchiveResult = true };
|
|
var service = new RendezVousService(repo);
|
|
|
|
await service.ArchiveAsync("my-guid-123");
|
|
|
|
Assert.Equal("my-guid-123", repo.LastArchiveId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ArchiveAsync_CancellationTokenTransmitted()
|
|
{
|
|
var repo = new FakeRendezVousRepository { ArchiveResult = true };
|
|
var service = new RendezVousService(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 FakeRendezVousRepository
|
|
{
|
|
ThrowOnArchive = new InvalidOperationException("Panne")
|
|
};
|
|
var service = new RendezVousService(repo);
|
|
|
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => service.ArchiveAsync("42"));
|
|
|
|
Assert.Equal("Panne", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ArchiveAsync_TransmetLAuteurAuRepository()
|
|
{
|
|
var repo = new FakeRendezVousRepository { ArchiveResult = true };
|
|
var service = new RendezVousService(repo);
|
|
var auteur = new AuditActor("7", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
|
|
|
|
await service.ArchiveAsync("42", auteur: auteur);
|
|
|
|
Assert.Same(auteur, repo.LastArchiveAuteur);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetTempsValideAsync_TransmetLAuteurAuRepository()
|
|
{
|
|
var repo = new FakeRendezVousRepository();
|
|
var service = new RendezVousService(repo);
|
|
var auteur = new AuditActor("7", "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
|
|
|
|
// Le passage au statut « Fait » modifie le noyau du rendez-vous : l'auteur
|
|
// doit atteindre le repository.
|
|
await service.SetTempsValideAsync("42", "collab-1", tempsValide: true, auteur: auteur);
|
|
|
|
Assert.Same(auteur, repo.LastSetTempsValideAuteur);
|
|
}
|
|
|
|
// =========================================================================
|
|
// ARCHITECTURE
|
|
// =========================================================================
|
|
|
|
[Fact]
|
|
public void Service_ImplementsIRendezVousService()
|
|
{
|
|
Assert.True(typeof(IRendezVousService).IsAssignableFrom(typeof(RendezVousService)));
|
|
}
|
|
|
|
[Fact]
|
|
public void Service_DependsOnIRendezVousRepository()
|
|
{
|
|
var type = typeof(RendezVousService);
|
|
bool hasRepoParam = type.GetConstructors()
|
|
.Any(c => c.GetParameters()
|
|
.Any(p => p.ParameterType == typeof(IRendezVousRepository)));
|
|
|
|
Assert.True(hasRepoParam, "RendezVousService doit dépendre de IRendezVousRepository.");
|
|
}
|
|
|
|
[Fact]
|
|
public void Service_HasNoOmikaDataReferences()
|
|
{
|
|
var type = typeof(RendezVousService);
|
|
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(RendezVousService);
|
|
var refs = GetReferencedTypes(type);
|
|
|
|
Assert.DoesNotContain(refs, t =>
|
|
t.Name.Contains("Batik", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Fact]
|
|
public void Service_HasNoHfsqlReferences()
|
|
{
|
|
var type = typeof(RendezVousService);
|
|
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(RendezVousService));
|
|
|
|
Assert.DoesNotContain("SELECT ", source, StringComparison.OrdinalIgnoreCase);
|
|
Assert.DoesNotContain("FROM ", source, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public void Service_HasNoApplicationRuntimeContext()
|
|
{
|
|
var type = typeof(RendezVousService);
|
|
var refs = GetReferencedTypes(type);
|
|
|
|
Assert.DoesNotContain(refs, t =>
|
|
t.Name.Contains("ApplicationRuntimeContext", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Fact]
|
|
public void Service_HasNoDatabaseSchemaVersion()
|
|
{
|
|
var type = typeof(RendezVousService);
|
|
var refs = GetReferencedTypes(type);
|
|
|
|
Assert.DoesNotContain(refs, t =>
|
|
t.Name.Contains("DatabaseSchemaVersion", StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
[Fact]
|
|
public void Service_HasNoOdbcReferences()
|
|
{
|
|
var type = typeof(RendezVousService);
|
|
var refs = GetReferencedTypes(type);
|
|
|
|
Assert.DoesNotContain(refs, t =>
|
|
t.Namespace is not null && t.Namespace.StartsWith("System.Data.Odbc", StringComparison.Ordinal));
|
|
}
|
|
|
|
// =========================================================================
|
|
// HELPERS
|
|
// =========================================================================
|
|
|
|
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 FakeRendezVousRepository : IRendezVousRepository
|
|
{
|
|
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 int CancelCallCount { get; private set; }
|
|
public RendezVousSearchCriteria? LastSearchCriteria { get; private set; }
|
|
public string? LastDetailsId { get; private set; }
|
|
public string? LastUpdateId { get; private set; }
|
|
public string? LastArchiveId { get; private set; }
|
|
public string? LastCancelId { get; private set; }
|
|
public RendezVousUpdateCommand? LastUpdateCommand { get; private set; }
|
|
public RendezVousCreateCommand? LastCreateCommand { get; private set; }
|
|
public AnnulationRendezVousCommand? LastCancelCommand { get; private set; }
|
|
public AuditActor? LastCancelAuteur { get; private set; }
|
|
public AuditActor? LastArchiveAuteur { get; private set; }
|
|
public AuditActor? LastSetTempsValideAuteur { get; private set; }
|
|
public CancellationToken LastUpdateCancellationToken { get; private set; }
|
|
public CancellationToken LastArchiveCancellationToken { get; private set; }
|
|
public CancellationToken LastCreateCancellationToken { get; private set; }
|
|
public CancellationToken LastCancelCancellationToken { get; private set; }
|
|
public CancellationToken LastCancellationToken { get; private set; }
|
|
public PagedResult<RendezVous>? SearchResult { get; set; }
|
|
public RendezVous? DetailsResult { get; set; }
|
|
public RendezVous? UpdateResult { get; set; }
|
|
public RendezVous? CreateResult { get; set; }
|
|
public bool ArchiveResult { get; set; }
|
|
public bool CancelResult { 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 Exception? ThrowOnCancel { get; set; }
|
|
|
|
public Task<RendezVous> CreateAsync(
|
|
RendezVousCreateCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
CreateCallCount++;
|
|
LastCreateCommand = command;
|
|
LastCreateCancellationToken = cancellationToken;
|
|
|
|
if (ThrowOnCreate is not null)
|
|
throw ThrowOnCreate;
|
|
|
|
return Task.FromResult(CreateResult ?? new RendezVous { Titre = command.Titre });
|
|
}
|
|
|
|
public Task<PagedResult<RendezVous>> SearchAsync(
|
|
RendezVousSearchCriteria criteria,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
SearchCallCount++;
|
|
LastSearchCriteria = criteria;
|
|
LastCancellationToken = cancellationToken;
|
|
|
|
if (ThrowOnSearch is not null)
|
|
throw ThrowOnSearch;
|
|
|
|
return Task.FromResult(SearchResult
|
|
?? new PagedResult<RendezVous>([], 1, 20, 0, 0));
|
|
}
|
|
|
|
public Task<PagedResult<RendezVous>> SearchDetailsAsync(
|
|
RendezVousSearchCriteria criteria,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
DetailsCallCount++;
|
|
LastSearchCriteria = criteria;
|
|
LastCancellationToken = cancellationToken;
|
|
|
|
if (ThrowOnSearch is not null)
|
|
throw ThrowOnSearch;
|
|
|
|
return Task.FromResult(SearchResult
|
|
?? new PagedResult<RendezVous>([], 1, 20, 0, 0));
|
|
}
|
|
|
|
public Task<RendezVous?> GetDetailsAsync(
|
|
string id,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
|
|
DetailsCallCount++;
|
|
LastDetailsId = id;
|
|
LastCancellationToken = cancellationToken;
|
|
|
|
if (ThrowOnDetails is not null)
|
|
throw ThrowOnDetails;
|
|
|
|
return Task.FromResult(DetailsResult);
|
|
}
|
|
|
|
public Task<RendezVous?> UpdateAsync(
|
|
string id, RendezVousUpdateCommand 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> CancelAsync(
|
|
string id, AnnulationRendezVousCommand annulation,
|
|
CancellationToken cancellationToken = default,
|
|
AuditActor? auteur = null)
|
|
{
|
|
CancelCallCount++;
|
|
LastCancelId = id;
|
|
LastCancelCommand = annulation;
|
|
LastCancelCancellationToken = cancellationToken;
|
|
LastCancelAuteur = auteur;
|
|
|
|
if (ThrowOnCancel is not null)
|
|
throw ThrowOnCancel;
|
|
|
|
return Task.FromResult(CancelResult);
|
|
}
|
|
|
|
public Task<bool> ArchiveAsync(
|
|
string id,
|
|
CancellationToken cancellationToken = default,
|
|
AuditActor? auteur = null)
|
|
{
|
|
ArchiveCallCount++;
|
|
LastArchiveId = id;
|
|
LastArchiveCancellationToken = cancellationToken;
|
|
LastArchiveAuteur = auteur;
|
|
|
|
if (ThrowOnArchive is not null)
|
|
throw ThrowOnArchive;
|
|
|
|
return Task.FromResult(ArchiveResult);
|
|
}
|
|
|
|
public Task<bool> SetTempsValideAsync(
|
|
string rendezVousId, string collaborateurId, bool tempsValide,
|
|
CancellationToken cancellationToken = default,
|
|
AuditActor? auteur = null)
|
|
{
|
|
LastSetTempsValideAuteur = auteur;
|
|
return Task.FromResult(true);
|
|
}
|
|
}
|
|
}
|