214 lines
10 KiB
C#
214 lines
10 KiB
C#
using Omika.Core.Compatibility;
|
|
using Omika.Core.Contracts.Common;
|
|
using Omika.Core.Contracts.Documents;
|
|
using Omika.Core.Contracts.Interventions;
|
|
using Omika.Core.Contracts.Mobile.Sync;
|
|
using Omika.Core.Domain;
|
|
using Omika.Core.Domain.Entities.Documents;
|
|
using Omika.Core.Interfaces;
|
|
using Omika.Core.Services;
|
|
using Omika.Data.Repositories;
|
|
|
|
namespace Omika.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// Prouve que trois rapports de même nom produisent trois fichiers distincts via
|
|
/// le mécanisme standard des documents (<see cref="DocumentPathResolver.WriteUniqueFile"/>),
|
|
/// SANS GUID dans le nom physique, avec nom en base == nom physique.
|
|
/// </summary>
|
|
public class RapportCreationServiceTests : IDisposable
|
|
{
|
|
private readonly string _dir;
|
|
|
|
public RapportCreationServiceTests()
|
|
{
|
|
_dir = Path.Combine(Path.GetTempPath(), "omika-rapport-collision", Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(_dir);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (Directory.Exists(_dir))
|
|
{
|
|
Directory.Delete(_dir, recursive: true);
|
|
}
|
|
}
|
|
|
|
private sealed class FakeModeleRapportRepository : IModeleRapportRepository
|
|
{
|
|
public Task<IReadOnlyList<ModeleRapport>> GetAllAsync(CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<ModeleRapport>>([]);
|
|
public Task<ModeleRapport?> GetByIdAsync(string id, CancellationToken ct = default)
|
|
=> Task.FromResult<ModeleRapport?>(null);
|
|
public Task<byte[]?> GetModeleDocxAsync(long modeleId, CancellationToken ct = default)
|
|
=> Task.FromResult<byte[]?>([1, 2, 3]);
|
|
}
|
|
|
|
private sealed class FakePdfService : IRapportPdfService
|
|
{
|
|
private int _calls;
|
|
public Task<byte[]> GenererPdfAsync(byte[] modeleDocx, IReadOnlyList<RapportChampDto> champsRemplis, CancellationToken ct = default)
|
|
{
|
|
// Contenu distinct à chaque génération pour prouver l'absence d'écrasement.
|
|
_calls++;
|
|
return Task.FromResult(new byte[] { (byte)_calls, 9, 9, 9 });
|
|
}
|
|
}
|
|
|
|
private sealed class FakeRapportChampService : IRapportChampService
|
|
{
|
|
public Task<IReadOnlyList<RapportChampDto>> GetChampsAsync(
|
|
GetRapportChampsRequest request, bool toutesRub = false, CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<RapportChampDto>>([]);
|
|
|
|
public Task<IReadOnlyList<RapportChampDto>> GetChampsMailAsync(
|
|
GetRapportChampsRequest request, CancellationToken ct = default)
|
|
=> Task.FromResult<IReadOnlyList<RapportChampDto>>([]);
|
|
}
|
|
|
|
private sealed class FakeInterventionService : IInterventionService
|
|
{
|
|
public Task<InterventionDetailsDto?> GetByIdAsync(string id, CancellationToken ct = default)
|
|
=> Task.FromResult<InterventionDetailsDto?>(new InterventionDetailsDto
|
|
{
|
|
Id = id,
|
|
SourceReference = LegacyEntityReference.FromLegacyId(7)
|
|
});
|
|
|
|
public Task<PagedResult<InterventionSummaryDto>> SearchAsync(SearchInterventionsRequest request, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<PagedResult<InterventionDetailsDto>> SearchDetailsAsync(SearchInterventionsRequest request, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<InterventionDetailsDto> CreateAsync(CreateInterventionRequest request, CancellationToken ct = default, AuditActor? auteur = null)
|
|
=> throw new NotImplementedException();
|
|
public Task<InterventionDetailsDto?> UpdateAsync(string id, UpdateInterventionRequest request, CancellationToken ct = default, AuditActor? auteur = null)
|
|
=> throw new NotImplementedException();
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<IReadOnlyList<NoteInterneDto>> GetNotesAsync(string interventionId, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<IReadOnlyList<NoteInterneDto>> GetNotesAdministrativesAsync(string interventionId, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<NoteInterneDto> AddNoteAsync(string interventionId, string contenu, AuditActor? auteur, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<IReadOnlyList<EmailInterventionDto>> GetEmailsAsync(string interventionId, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<EmailInterventionDto> AddEmailAsync(string interventionId, CreateEmailInterventionRequest request, AuditActor? auteur, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
}
|
|
|
|
private sealed class FakeDocumentRepository : IDocumentRepository
|
|
{
|
|
private readonly string _directory;
|
|
|
|
public FakeDocumentRepository(string directory) => _directory = directory;
|
|
|
|
public List<Document> Documents { get; } = [];
|
|
|
|
public Task<DocumentWriteResult?> WriteDocumentFileAsync(
|
|
long interventionId, long categorieId, string fileName, byte[] content, CancellationToken ct = default)
|
|
{
|
|
// Réutilise EXACTEMENT le mécanisme standard de collision des documents.
|
|
string? finalName = DocumentPathResolver.WriteUniqueFile(_directory, fileName, content);
|
|
return Task.FromResult<DocumentWriteResult?>(
|
|
finalName is null ? null : new DocumentWriteResult(_directory, finalName));
|
|
}
|
|
|
|
public Task<Document> CreateAsync(Document document, CancellationToken ct = default)
|
|
{
|
|
Documents.Add(document);
|
|
return Task.FromResult(document);
|
|
}
|
|
|
|
public Task<long> ResolveCreationUserAsync(AuditActor? auteur, CancellationToken ct = default)
|
|
=> Task.FromResult(1L);
|
|
public Task<long> ResolveCategoryIdAsync(string categorieCle, CancellationToken ct = default)
|
|
=> Task.FromResult(2L);
|
|
|
|
public Task<PagedResult<Document>> SearchAsync(PagedRequest request, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<IReadOnlyList<Document>> GetByInterventionAsync(PhysicalEntityIdentity interventionIdentity, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<IReadOnlyList<Document>> GetByInterventionsAsync(IReadOnlyList<long> numericInterventionIds, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<Document?> GetByIdAsync(string id, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<Document?> GetByPhysicalIdentityAsync(PhysicalEntityIdentity identity, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<string?> ResolveDocumentFullPathAsync(Document document, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<IReadOnlyList<NoteInterneDto>> GetNotesAsync(long documentId, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<NoteInterneDto> AddNoteAsync(long documentId, string contenu, long creationUser, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TroisRapportsMemeNom_TroisFichiersDistincts_SansGuid()
|
|
{
|
|
var documentRepository = new FakeDocumentRepository(_dir);
|
|
var service = new RapportCreationService(
|
|
new FakeModeleRapportRepository(),
|
|
new FakePdfService(),
|
|
documentRepository,
|
|
new FakeInterventionService(),
|
|
new FakeRapportChampService());
|
|
|
|
DocumentSummaryDto? premier = await service.CreerAsync(new CreateRapportRequest
|
|
{
|
|
ModeleId = "42",
|
|
InterventionId = Guid.NewGuid().ToString(),
|
|
Nom = "Rapport intervention",
|
|
RapportGuid = Guid.NewGuid()
|
|
}, auteur: null);
|
|
|
|
DocumentSummaryDto? second = await service.CreerAsync(new CreateRapportRequest
|
|
{
|
|
ModeleId = "42",
|
|
InterventionId = Guid.NewGuid().ToString(),
|
|
Nom = "Rapport intervention",
|
|
RapportGuid = Guid.NewGuid()
|
|
}, auteur: null);
|
|
|
|
DocumentSummaryDto? troisieme = await service.CreerAsync(new CreateRapportRequest
|
|
{
|
|
ModeleId = "42",
|
|
InterventionId = Guid.NewGuid().ToString(),
|
|
Nom = "Rapport intervention",
|
|
RapportGuid = Guid.NewGuid()
|
|
}, auteur: null);
|
|
|
|
Assert.NotNull(premier);
|
|
Assert.NotNull(second);
|
|
Assert.NotNull(troisieme);
|
|
|
|
// Noms physiques = noms en base, sans GUID.
|
|
Assert.Equal("Rapport intervention.pdf", premier!.Nom);
|
|
Assert.Equal("Rapport intervention_1.pdf", second!.Nom);
|
|
Assert.Equal("Rapport intervention_2.pdf", troisieme!.Nom);
|
|
|
|
// Les trois fichiers existent simultanément.
|
|
string[] files = Directory.GetFiles(_dir);
|
|
Assert.Equal(3, files.Length);
|
|
Assert.Contains(Path.Combine(_dir, "Rapport intervention.pdf"), files);
|
|
Assert.Contains(Path.Combine(_dir, "Rapport intervention_1.pdf"), files);
|
|
Assert.Contains(Path.Combine(_dir, "Rapport intervention_2.pdf"), files);
|
|
|
|
// Chaque fichier a son propre contenu (aucun écrasement).
|
|
byte[] content1 = await File.ReadAllBytesAsync(Path.Combine(_dir, "Rapport intervention.pdf"));
|
|
byte[] content2 = await File.ReadAllBytesAsync(Path.Combine(_dir, "Rapport intervention_1.pdf"));
|
|
byte[] content3 = await File.ReadAllBytesAsync(Path.Combine(_dir, "Rapport intervention_2.pdf"));
|
|
Assert.Equal((byte)1, content1[0]);
|
|
Assert.Equal((byte)2, content2[0]);
|
|
Assert.Equal((byte)3, content3[0]);
|
|
|
|
// DB : nom en base == nom physique (3 enregistrements distincts).
|
|
Assert.Equal(3, documentRepository.Documents.Count);
|
|
Assert.Equal("Rapport intervention.pdf", documentRepository.Documents[0].Nom);
|
|
Assert.Equal("Rapport intervention_1.pdf", documentRepository.Documents[1].Nom);
|
|
Assert.Equal("Rapport intervention_2.pdf", documentRepository.Documents[2].Nom);
|
|
}
|
|
}
|