Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
@@ -0,0 +1,213 @@
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);
}
}