699 lines
25 KiB
C#
699 lines
25 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Options;
|
|
using Omika.Core.Compatibility;
|
|
using Omika.Core.Configuration;
|
|
using Omika.Core.Constants;
|
|
using Omika.Core.Contracts.Common;
|
|
using Omika.Core.Contracts.Documents;
|
|
using Omika.Core.Contracts.Mobile.Documents;
|
|
using Omika.Core.Contracts.Mobile.Sync;
|
|
using Omika.Core.Domain.Entities.Documents;
|
|
using Omika.Core.Domain.ValueObjects;
|
|
using Omika.Core.Interfaces;
|
|
using Omika.Core.Interfaces.Mobile;
|
|
using Omika.Core.Services.Mobile;
|
|
|
|
using Omika.Core.Domain;
|
|
|
|
namespace Omika.Tests.Mobile;
|
|
|
|
/// <summary>
|
|
/// Tests du service de documents mobile.
|
|
/// </summary>
|
|
public class MobileDocumentServiceTests
|
|
{
|
|
private static Document ValidDocument(Guid id)
|
|
{
|
|
return new Document
|
|
{
|
|
Id = id,
|
|
Nom = "rapport.pdf",
|
|
MimeType = "application/pdf",
|
|
Extension = ".pdf",
|
|
TailleOctets = 1024,
|
|
CheminLogique = "mobile/abc",
|
|
DateCreation = DateTime.Now
|
|
};
|
|
}
|
|
|
|
private static MobileDocumentService CreateService(
|
|
FakeDocumentRepository repo,
|
|
FakeFileStorage? fileStorage = null,
|
|
IMobileEntityIdentityResolver? resolver = null,
|
|
DocumentStorageOptions? storageOptions = null)
|
|
=> new(
|
|
repo,
|
|
resolver ?? new FakeIdentityResolver(),
|
|
fileStorage ?? new FakeFileStorage(),
|
|
Options.Create(storageOptions ?? new DocumentStorageOptions { MaxUploadSizeBytes = 1024 * 1024 }),
|
|
NullLogger<MobileDocumentService>.Instance);
|
|
|
|
[Fact]
|
|
public async Task Download_Document_Trouve_Returns_Metadata()
|
|
{
|
|
// Arrange
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { Document = ValidDocument(guid) };
|
|
var service = CreateService(repo);
|
|
|
|
// Act
|
|
var result = await service.DownloadAsync(guid);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal(guid, result!.DocumentGuid);
|
|
Assert.Equal("rapport.pdf", result.NomFichier);
|
|
Assert.Equal("application/pdf", result.MimeType);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Download_Document_Inconnu_Returns_Null()
|
|
{
|
|
// Arrange
|
|
var repo = new FakeDocumentRepository();
|
|
var service = CreateService(repo);
|
|
|
|
// Act
|
|
var result = await service.DownloadAsync(Guid.NewGuid());
|
|
|
|
// Assert
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Download_Empty_Guid_Returns_Null()
|
|
{
|
|
// Arrange
|
|
var repo = new FakeDocumentRepository();
|
|
var service = CreateService(repo);
|
|
|
|
// Act
|
|
var result = await service.DownloadAsync(Guid.Empty);
|
|
|
|
// Assert
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Download_Returns_Binary_Content()
|
|
{
|
|
// Arrange
|
|
var guid = Guid.NewGuid();
|
|
var content = new byte[] { 1, 2, 3, 4, 5 };
|
|
string tempPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"omika-test-{guid:N}.bin");
|
|
await System.IO.File.WriteAllBytesAsync(tempPath, content);
|
|
|
|
var repo = new FakeDocumentRepository { Document = ValidDocument(guid), ResolvedPath = tempPath };
|
|
var service = CreateService(repo);
|
|
|
|
// Act
|
|
var result = await service.DownloadAsync(guid);
|
|
|
|
// Assert : le contenu binaire est lu depuis le chemin physique résolu.
|
|
Assert.NotNull(result);
|
|
Assert.Equal(5, result!.TailleOctets);
|
|
Assert.Equal(content, result.Contenu);
|
|
Assert.True(result.Existe);
|
|
|
|
System.IO.File.Delete(tempPath);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Download_Resolves_By_Physical_Identity()
|
|
{
|
|
// Arrange
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { Document = ValidDocument(guid) };
|
|
var service = CreateService(repo);
|
|
|
|
// Act
|
|
var result = await service.DownloadAsync(guid);
|
|
|
|
// Assert : la résolution passe par l'identité physique (GUID).
|
|
Assert.NotNull(result);
|
|
Assert.Equal(guid, repo.RequestedPhysicalGuid);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_Single_Document_Returns_Success()
|
|
{
|
|
// Arrange
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { WrittenFilePath = "Intervention 1/Photos #1" };
|
|
var fileStorage = new FakeFileStorage();
|
|
var service = CreateService(repo, fileStorage);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "photo.jpg",
|
|
MimeType = "image/jpeg",
|
|
Extension = ".jpg",
|
|
Categorie = MobilePermissionKeys.DocCategoriePhoto,
|
|
Contenu = new byte[] { 1, 2, 3, 4 }
|
|
};
|
|
|
|
// Act
|
|
var result = await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, new AuditActor(null, "collab-1"));
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.Equal(guid, result.DocumentGuid);
|
|
Assert.True(repo.Created);
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.Equal(MobilePermissionKeys.DocCategoriePhoto, repo.CreatedDocument!.Categorie?.Key);
|
|
Assert.Equal("Intervention 1/Photos #1", repo.CreatedDocument.CheminLogique);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_CheminServeurIndisponible_RepliSurStockage()
|
|
{
|
|
// Arrange : le répertoire serveur décodé n'est pas résolvable (racine non
|
|
// configurée) → repli sur le stockage historique, le document est créé.
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository(); // WrittenFilePath = null
|
|
var fileStorage = new FakeFileStorage();
|
|
var service = CreateService(repo, fileStorage);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "photo.png",
|
|
MimeType = "image/png",
|
|
Extension = ".png",
|
|
Categorie = MobilePermissionKeys.DocCategoriePhoto,
|
|
Contenu = new byte[] { 1, 2, 3 }
|
|
};
|
|
|
|
// Act
|
|
var result = await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert : succès malgré l'absence de chemin serveur, fichier écrit en repli.
|
|
Assert.True(result.Success);
|
|
Assert.True(fileStorage.Written);
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.Equal(MobilePermissionKeys.DocCategoriePhoto, repo.CreatedDocument!.Categorie?.Key);
|
|
Assert.Equal("mobile/abc", repo.CreatedDocument.CheminLogique);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_Preserves_Provided_Guid()
|
|
{
|
|
// Arrange
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { WrittenFilePath = "Intervention 1/Autre document #2" };
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "doc.txt",
|
|
Categorie = MobilePermissionKeys.DocCategorieAutre,
|
|
Contenu = new byte[] { 1 }
|
|
};
|
|
|
|
// Act
|
|
var result = await service.UploadAsync(request, MobilePermissionKeys.DocCategorieAutre, null);
|
|
|
|
// Assert : le serveur conserve le GUID fourni par le mobile.
|
|
Assert.True(result.Success);
|
|
Assert.Equal(guid, result.DocumentGuid);
|
|
Assert.Equal(guid, repo.CreatedDocument!.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_Empty_Guid_Returns_Error()
|
|
{
|
|
// Arrange
|
|
var repo = new FakeDocumentRepository();
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = Guid.Empty,
|
|
NomFichier = "doc.txt",
|
|
Contenu = new byte[] { 1 }
|
|
};
|
|
|
|
// Act
|
|
var result = await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal("DOCUMENT_GUID_INVALID", result.ErrorCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_Invalid_FileName_Returns_Error()
|
|
{
|
|
// Arrange
|
|
var repo = new FakeDocumentRepository();
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = Guid.NewGuid(),
|
|
NomFichier = " ",
|
|
Contenu = new byte[] { 1 }
|
|
};
|
|
|
|
// Act
|
|
var result = await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert
|
|
Assert.False(result.Success);
|
|
Assert.Equal("DOCUMENT_FILENAME_INVALID", result.ErrorCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_Write_Error_Returns_Failure()
|
|
{
|
|
// Arrange
|
|
var repo = new FakeDocumentRepository { ThrowOnCreate = true, WrittenFilePath = "Intervention 1/Photos #1" };
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = Guid.NewGuid(),
|
|
NomFichier = "doc.txt",
|
|
Contenu = new byte[] { 1 }
|
|
};
|
|
|
|
// Act
|
|
var result = await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert : erreur d'écriture disque → échec.
|
|
Assert.False(result.Success);
|
|
Assert.Equal("DOCUMENT_UPLOAD_FAILED", result.ErrorCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_Size_Exceeded_Returns_Error()
|
|
{
|
|
// Arrange
|
|
var repo = new FakeDocumentRepository();
|
|
var service = CreateService(
|
|
repo,
|
|
storageOptions: new DocumentStorageOptions { MaxUploadSizeBytes = 2 });
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = Guid.NewGuid(),
|
|
NomFichier = "doc.txt",
|
|
Contenu = new byte[] { 1, 2, 3 }
|
|
};
|
|
|
|
// Act
|
|
var result = await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert : taille maximale dépassée → échec.
|
|
Assert.False(result.Success);
|
|
Assert.Equal("DOCUMENT_SIZE_EXCEEDED", result.ErrorCode);
|
|
}
|
|
|
|
// ===== Fakes =====
|
|
|
|
private sealed class FakeIdentityResolver : IMobileEntityIdentityResolver
|
|
{
|
|
public Guid CreateCanonicalIdentity(string entityType, PhysicalEntityIdentity physical)
|
|
=> physical.Guid ?? Guid.NewGuid();
|
|
|
|
public PhysicalEntityIdentity? ResolvePhysicalIdentity(string entityType, Guid publicGuid)
|
|
=> publicGuid == Guid.Empty ? null : new PhysicalEntityIdentity { Guid = publicGuid };
|
|
|
|
public bool IsCanonical(string entityType, Guid publicGuid)
|
|
=> publicGuid != Guid.Empty;
|
|
}
|
|
|
|
private sealed class FakeFileStorage : IDocumentFileStorage
|
|
{
|
|
public byte[]? Content { get; set; }
|
|
public bool Written { get; private set; }
|
|
public bool ThrowOnSizeExceeded { get; set; }
|
|
|
|
public byte[]? Read(string? relativeDirectory, string? fileName)
|
|
=> Content;
|
|
|
|
public string Write(string fileName, byte[] content)
|
|
{
|
|
if (ThrowOnSizeExceeded)
|
|
{
|
|
throw new InvalidOperationException("Taille maximale dépassée.");
|
|
}
|
|
Written = true;
|
|
return "mobile/abc";
|
|
}
|
|
|
|
public string? SanitizeFileName(string? fileName)
|
|
=> string.IsNullOrWhiteSpace(fileName) ? null : fileName.Trim();
|
|
}
|
|
|
|
private sealed class FakeDocumentRepository : IDocumentRepository
|
|
{
|
|
public Document? Document { get; set; }
|
|
public bool Created { get; private set; }
|
|
public Document? CreatedDocument { get; private set; }
|
|
public bool ThrowOnCreate { get; set; }
|
|
public Guid? RequestedId { get; private set; }
|
|
public Guid? RequestedPhysicalGuid { get; private set; }
|
|
|
|
public Task<PagedResult<Document>> SearchAsync(PagedRequest request, CancellationToken cancellationToken = default)
|
|
{
|
|
var items = Document is null ? Array.Empty<Document>() : new[] { Document };
|
|
return Task.FromResult(new PagedResult<Document>(items, 1, 1000, items.Length, 1));
|
|
}
|
|
|
|
public Task<IReadOnlyList<Document>> GetByInterventionAsync(PhysicalEntityIdentity interventionIdentity, CancellationToken cancellationToken = default)
|
|
{
|
|
var items = Document is null ? Array.Empty<Document>() : new[] { Document };
|
|
return Task.FromResult<IReadOnlyList<Document>>(items);
|
|
}
|
|
|
|
public Task<IReadOnlyList<Document>> GetByInterventionsAsync(IReadOnlyList<long> numericInterventionIds, CancellationToken cancellationToken = default)
|
|
{
|
|
var items = Document is null ? Array.Empty<Document>() : new[] { Document };
|
|
return Task.FromResult<IReadOnlyList<Document>>(items);
|
|
}
|
|
|
|
public Task<Document?> GetByIdAsync(string id, CancellationToken cancellationToken = default)
|
|
{
|
|
RequestedId = Guid.TryParse(id, out var g) ? g : null;
|
|
return Task.FromResult(Document);
|
|
}
|
|
|
|
public Task<Document?> GetByPhysicalIdentityAsync(PhysicalEntityIdentity identity, CancellationToken cancellationToken = default)
|
|
{
|
|
RequestedPhysicalGuid = identity.Guid;
|
|
return Task.FromResult(Document);
|
|
}
|
|
|
|
public Task<Document> CreateAsync(Document document, CancellationToken cancellationToken = default)
|
|
{
|
|
if (ThrowOnCreate)
|
|
{
|
|
throw new IOException("Erreur d'écriture disque simulée.");
|
|
}
|
|
Created = true;
|
|
CreatedDocument = document;
|
|
return Task.FromResult(document);
|
|
}
|
|
|
|
public Task<bool> ArchiveAsync(string id, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(true);
|
|
|
|
public string? ResolvedPath { get; set; }
|
|
|
|
public Task<string?> ResolveDocumentFullPathAsync(Document document, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult(ResolvedPath);
|
|
|
|
public Task<IReadOnlyList<NoteInterneDto>> GetNotesAsync(long documentId, CancellationToken cancellationToken = default)
|
|
=> Task.FromResult<IReadOnlyList<NoteInterneDto>>([]);
|
|
|
|
public long LastNoteCreationUser { get; private set; }
|
|
|
|
public Task<NoteInterneDto> AddNoteAsync(long documentId, string contenu, long creationUser, CancellationToken cancellationToken = default)
|
|
{
|
|
LastNoteCreationUser = creationUser;
|
|
return Task.FromResult(new NoteInterneDto());
|
|
}
|
|
|
|
public long ResolvedCreationUser { get; set; }
|
|
|
|
public Task<long> ResolveCreationUserAsync(AuditActor? auteur, CancellationToken cancellationToken = default)
|
|
{
|
|
// Simule le résolveur central : UserId prioritaire, sinon valeur fixe.
|
|
if (long.TryParse(auteur?.UserId, out long utPk) && utPk > 0)
|
|
{
|
|
return Task.FromResult(utPk);
|
|
}
|
|
|
|
return Task.FromResult(ResolvedCreationUser);
|
|
}
|
|
|
|
public long ResolvedCategoryId { get; set; }
|
|
public string? ResolvedCategoryCle { get; private set; }
|
|
|
|
public Task<long> ResolveCategoryIdAsync(string categorieCle, CancellationToken cancellationToken = default)
|
|
{
|
|
ResolvedCategoryCle = categorieCle;
|
|
return Task.FromResult(ResolvedCategoryId);
|
|
}
|
|
|
|
public string? WrittenFilePath { get; set; }
|
|
public string? WrittenFileName { get; set; }
|
|
public long WrittenCategorieId { get; private set; }
|
|
|
|
public Task<DocumentWriteResult?> WriteDocumentFileAsync(long interventionId, long categorieId, string fileName, byte[] content, CancellationToken cancellationToken = default)
|
|
{
|
|
WrittenCategorieId = categorieId;
|
|
if (WrittenFilePath is null)
|
|
{
|
|
return Task.FromResult<DocumentWriteResult?>(null);
|
|
}
|
|
|
|
return Task.FromResult<DocumentWriteResult?>(
|
|
new DocumentWriteResult(WrittenFilePath, WrittenFileName ?? fileName));
|
|
}
|
|
}
|
|
|
|
// ============================ Date métier du document ============================
|
|
|
|
[Fact]
|
|
public async Task Upload_DateFournieParLAppareil_EstEnregistreeTelleQuelle()
|
|
{
|
|
// Arrange : photo prise le 10/09/2026 à 14:32, synchronisée plus tard. Le
|
|
// serveur ne doit PAS substituer l'instant de l'upload.
|
|
var priseDeVue = new DateTime(2026, 9, 10, 14, 32, 18);
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { WrittenFilePath = "Photos #1" };
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "IMG_4521.jpg",
|
|
MimeType = "image/jpeg",
|
|
Extension = ".jpg",
|
|
Categorie = MobilePermissionKeys.DocCategoriePhoto,
|
|
Contenu = new byte[] { 1, 2, 3 },
|
|
DateCreation = priseDeVue
|
|
};
|
|
|
|
// Act
|
|
var result = await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.Equal(priseDeVue, repo.CreatedDocument!.DateCreation);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_SansDate_UtiliseLaDateCouranteDuServeur()
|
|
{
|
|
// Arrange : client antérieur à la fonctionnalité, ou opération Outbox
|
|
// historique : le comportement d'origine est conservé.
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { WrittenFilePath = "Photos #1" };
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "photo.jpg",
|
|
MimeType = "image/jpeg",
|
|
Extension = ".jpg",
|
|
Categorie = MobilePermissionKeys.DocCategoriePhoto,
|
|
Contenu = new byte[] { 1, 2, 3 }
|
|
};
|
|
|
|
DateTime avant = DateTime.Now.AddSeconds(-1);
|
|
|
|
// Act
|
|
await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.InRange(repo.CreatedDocument!.DateCreation, avant, DateTime.Now.AddSeconds(5));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_DateDansLeFutur_EstRejeteeAuProfitDeLaDateServeur()
|
|
{
|
|
// Arrange : horloge d'appareil déréglée ou client défaillant. Une date
|
|
// fausse corromprait les tris : on préfère la date serveur.
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { WrittenFilePath = "Photos #1" };
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "photo.jpg",
|
|
MimeType = "image/jpeg",
|
|
Extension = ".jpg",
|
|
Categorie = MobilePermissionKeys.DocCategoriePhoto,
|
|
Contenu = new byte[] { 1, 2, 3 },
|
|
DateCreation = DateTime.Now.AddYears(10)
|
|
};
|
|
|
|
DateTime avant = DateTime.Now.AddSeconds(-1);
|
|
|
|
// Act
|
|
await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert : repli sur la date serveur, l'upload n'échoue pas pour autant.
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.InRange(repo.CreatedDocument!.DateCreation, avant, DateTime.Now.AddSeconds(5));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_DateAberrante_EstRejeteeAuProfitDeLaDateServeur()
|
|
{
|
|
// Arrange : valeur sentinelle (métadonnée illisible).
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { WrittenFilePath = "Photos #1" };
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "photo.jpg",
|
|
MimeType = "image/jpeg",
|
|
Extension = ".jpg",
|
|
Categorie = MobilePermissionKeys.DocCategoriePhoto,
|
|
Contenu = new byte[] { 1, 2, 3 },
|
|
DateCreation = DateTime.MinValue
|
|
};
|
|
|
|
DateTime avant = DateTime.Now.AddSeconds(-1);
|
|
|
|
// Act
|
|
await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.InRange(repo.CreatedDocument!.DateCreation, avant, DateTime.Now.AddSeconds(5));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_DateAncienne_MaisValide_EstConservee()
|
|
{
|
|
// Arrange : photo prise en 2019, ajoutée aujourd'hui. C'est le cas métier
|
|
// central : la date ancienne doit survivre.
|
|
var ancienne = new DateTime(2019, 5, 14, 8, 3, 0);
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { WrittenFilePath = "Photos #1" };
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "IMG_001.jpg",
|
|
MimeType = "image/jpeg",
|
|
Extension = ".jpg",
|
|
Categorie = MobilePermissionKeys.DocCategoriePhoto,
|
|
Contenu = new byte[] { 1, 2, 3 },
|
|
DateCreation = ancienne
|
|
};
|
|
|
|
// Act
|
|
await service.UploadAsync(request, MobilePermissionKeys.DocCategoriePhoto, null);
|
|
|
|
// Assert
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.Equal(ancienne, repo.CreatedDocument!.DateCreation);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_ServeurRenomme_UtiliseNomFinalEnBase()
|
|
{
|
|
// Arrange : le serveur a rendu le nom unique (collision) → Viamichelin_1.pdf.
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository
|
|
{
|
|
WrittenFilePath = "Intervention #1",
|
|
WrittenFileName = "Viamichelin_1.pdf"
|
|
};
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "Viamichelin.pdf",
|
|
MimeType = "application/pdf",
|
|
Extension = ".pdf",
|
|
Categorie = MobilePermissionKeys.DocCategorieAutre,
|
|
Contenu = new byte[] { 1, 2, 3 }
|
|
};
|
|
|
|
// Act
|
|
var result = await service.UploadAsync(request, MobilePermissionKeys.DocCategorieAutre, null);
|
|
|
|
// Assert : le nom en base est EXACTEMENT le nom physique final, et le
|
|
// titre (affichage mobile) reprend ce même nom canonique.
|
|
Assert.True(result.Success);
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.Equal("Viamichelin_1.pdf", repo.CreatedDocument!.Nom);
|
|
Assert.Equal("Viamichelin_1.pdf", repo.CreatedDocument!.Titre);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_SansRenommage_ConserveNomOriginalEnBase()
|
|
{
|
|
// Arrange : aucune collision → le nom original est conservé tel quel.
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository
|
|
{
|
|
WrittenFilePath = "Intervention #1",
|
|
WrittenFileName = "Viamichelin.pdf"
|
|
};
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "Viamichelin.pdf",
|
|
MimeType = "application/pdf",
|
|
Extension = ".pdf",
|
|
Categorie = MobilePermissionKeys.DocCategorieAutre,
|
|
Contenu = new byte[] { 1, 2, 3 }
|
|
};
|
|
|
|
// Act
|
|
await service.UploadAsync(request, MobilePermissionKeys.DocCategorieAutre, null);
|
|
|
|
// Assert
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.Equal("Viamichelin.pdf", repo.CreatedDocument!.Nom);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Upload_UserIdPrioritaire_EstUtilisePourCreationUser()
|
|
{
|
|
// Arrange : UserId = 57 (UT_PKID), CollaborateurId = 123 (CO_PKID différent).
|
|
var guid = Guid.NewGuid();
|
|
var repo = new FakeDocumentRepository { WrittenFilePath = "Intervention #1" };
|
|
var service = CreateService(repo);
|
|
var request = new DocumentUploadRequest
|
|
{
|
|
DocumentGuid = guid,
|
|
NomFichier = "doc.pdf",
|
|
MimeType = "application/pdf",
|
|
Extension = ".pdf",
|
|
Categorie = MobilePermissionKeys.DocCategorieAutre,
|
|
Contenu = new byte[] { 1, 2, 3 }
|
|
};
|
|
|
|
// Act
|
|
await service.UploadAsync(request, MobilePermissionKeys.DocCategorieAutre, new AuditActor("57", "123"));
|
|
|
|
// Assert : CreationUser = 57 (UT_PKID), JAMAIS 123 (CO_PKID).
|
|
Assert.NotNull(repo.CreatedDocument);
|
|
Assert.Equal(57, repo.CreatedDocument!.CreationUserNumericId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddNote_UserIdPrioritaire_EstUtilisePourCreationUser()
|
|
{
|
|
// Arrange : le document existe physiquement (SourceReference = PK numérique).
|
|
var guid = Guid.NewGuid();
|
|
var doc = ValidDocument(guid);
|
|
doc.SourceReference = LegacyEntityReference.FromLegacyId(123);
|
|
var repo = new FakeDocumentRepository { Document = doc };
|
|
var service = CreateService(repo);
|
|
|
|
// Act
|
|
await service.AddNoteAsync(guid, "note", new AuditActor("57", "123"));
|
|
|
|
// Assert : la note est créée avec CreationUser = 57 (UT_PKID, jamais CO_PKID).
|
|
Assert.Equal(57, repo.LastNoteCreationUser);
|
|
}
|
|
}
|