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; /// /// Prouve que trois rapports de même nom produisent trois fichiers distincts via /// le mécanisme standard des documents (), /// SANS GUID dans le nom physique, avec nom en base == nom physique. /// 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> GetAllAsync(CancellationToken ct = default) => Task.FromResult>([]); public Task GetByIdAsync(string id, CancellationToken ct = default) => Task.FromResult(null); public Task GetModeleDocxAsync(long modeleId, CancellationToken ct = default) => Task.FromResult([1, 2, 3]); } private sealed class FakePdfService : IRapportPdfService { private int _calls; public Task GenererPdfAsync(byte[] modeleDocx, IReadOnlyList 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> GetChampsAsync( GetRapportChampsRequest request, bool toutesRub = false, CancellationToken ct = default) => Task.FromResult>([]); public Task> GetChampsMailAsync( GetRapportChampsRequest request, CancellationToken ct = default) => Task.FromResult>([]); } private sealed class FakeInterventionService : IInterventionService { public Task GetByIdAsync(string id, CancellationToken ct = default) => Task.FromResult(new InterventionDetailsDto { Id = id, SourceReference = LegacyEntityReference.FromLegacyId(7) }); public Task> SearchAsync(SearchInterventionsRequest request, CancellationToken ct = default) => throw new NotImplementedException(); public Task> SearchDetailsAsync(SearchInterventionsRequest request, CancellationToken ct = default) => throw new NotImplementedException(); public Task CreateAsync(CreateInterventionRequest request, CancellationToken ct = default, AuditActor? auteur = null) => throw new NotImplementedException(); public Task UpdateAsync(string id, UpdateInterventionRequest request, CancellationToken ct = default, AuditActor? auteur = null) => throw new NotImplementedException(); public Task ArchiveAsync(string id, CancellationToken ct = default) => throw new NotImplementedException(); public Task> GetNotesAsync(string interventionId, CancellationToken ct = default) => throw new NotImplementedException(); public Task> GetNotesAdministrativesAsync(string interventionId, CancellationToken ct = default) => throw new NotImplementedException(); public Task AddNoteAsync(string interventionId, string contenu, AuditActor? auteur, CancellationToken ct = default) => throw new NotImplementedException(); public Task> GetEmailsAsync(string interventionId, CancellationToken ct = default) => throw new NotImplementedException(); public Task 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 Documents { get; } = []; public Task 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( finalName is null ? null : new DocumentWriteResult(_directory, finalName)); } public Task CreateAsync(Document document, CancellationToken ct = default) { Documents.Add(document); return Task.FromResult(document); } public Task ResolveCreationUserAsync(AuditActor? auteur, CancellationToken ct = default) => Task.FromResult(1L); public Task ResolveCategoryIdAsync(string categorieCle, CancellationToken ct = default) => Task.FromResult(2L); public Task> SearchAsync(PagedRequest request, CancellationToken ct = default) => throw new NotImplementedException(); public Task> GetByInterventionAsync(PhysicalEntityIdentity interventionIdentity, CancellationToken ct = default) => throw new NotImplementedException(); public Task> GetByInterventionsAsync(IReadOnlyList numericInterventionIds, CancellationToken ct = default) => throw new NotImplementedException(); public Task GetByIdAsync(string id, CancellationToken ct = default) => throw new NotImplementedException(); public Task GetByPhysicalIdentityAsync(PhysicalEntityIdentity identity, CancellationToken ct = default) => throw new NotImplementedException(); public Task ArchiveAsync(string id, CancellationToken ct = default) => throw new NotImplementedException(); public Task ResolveDocumentFullPathAsync(Document document, CancellationToken ct = default) => throw new NotImplementedException(); public Task> GetNotesAsync(long documentId, CancellationToken ct = default) => throw new NotImplementedException(); public Task 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); } }