Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Omika.Core.Configuration;
|
||||
using Omika.Core.Contracts.Mobile.Languages;
|
||||
using Omika.Core.Services.Mobile;
|
||||
|
||||
namespace Omika.Tests.Mobile;
|
||||
|
||||
/// <summary>
|
||||
/// Tests du service de téléchargement des modèles de langue (ONNX).
|
||||
/// Vérifie le chargement du manifeste, le remplissage des URLs relatives et la
|
||||
/// résolution de fichiers avec protection anti path traversal.
|
||||
/// </summary>
|
||||
public class LanguageModelServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetManifest_Absent_RetourneNull()
|
||||
{
|
||||
string root = CreateRoot();
|
||||
try
|
||||
{
|
||||
var service = CreateService(root);
|
||||
|
||||
var result = await service.GetManifestAsync();
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetManifest_Present_RemplitUrlsRelatives()
|
||||
{
|
||||
string root = CreateRoot();
|
||||
try
|
||||
{
|
||||
string manifest = """
|
||||
{
|
||||
"packs": [
|
||||
{
|
||||
"code": "es",
|
||||
"libelle": "Espagnol",
|
||||
"version": "1.0.0",
|
||||
"fichiers": [
|
||||
{ "sens": "versFrancais", "nomFichier": "encoder.onnx", "tailleOctets": 123, "sha256": "abc" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
await File.WriteAllTextAsync(Path.Combine(root, "manifest.json"), manifest);
|
||||
var service = CreateService(root);
|
||||
|
||||
var result = await service.GetManifestAsync();
|
||||
|
||||
Assert.NotNull(result);
|
||||
var pack = Assert.Single(result.Packs);
|
||||
Assert.Equal("es", pack.Code);
|
||||
Assert.Equal("Espagnol", pack.Libelle);
|
||||
Assert.Equal("1.0.0", pack.Version);
|
||||
var fichier = Assert.Single(pack.Fichiers);
|
||||
Assert.Equal("/api/langues/es/versFrancais/encoder.onnx", fichier.Url);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveFile_Existant_RetourneCheminEtContenu()
|
||||
{
|
||||
string root = CreateRoot();
|
||||
try
|
||||
{
|
||||
string dir = Path.Combine(root, "es", "versFrancais");
|
||||
Directory.CreateDirectory(dir);
|
||||
await File.WriteAllTextAsync(Path.Combine(dir, "encoder.onnx"), "fake-model");
|
||||
var service = CreateService(root);
|
||||
|
||||
var result = await service.ResolveFileAsync("es", "versFrancais", "encoder.onnx");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("encoder.onnx", result.FileName);
|
||||
Assert.Equal("application/octet-stream", result.ContentType);
|
||||
Assert.Equal("fake-model", await File.ReadAllTextAsync(result.FullPath));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveFile_RootPathVide_RetombeSurDossierApplication()
|
||||
{
|
||||
// Une RootPath vide (section « Langues:RootPath » laissée vide) ne doit pas
|
||||
// résoudre relativement au répertoire de travail courant (System32 en service
|
||||
// Windows), mais relativement au dossier de l'application.
|
||||
var service = new LanguageModelService(
|
||||
Options.Create(new LanguageModelOptions { RootPath = "", ManifestFileName = "manifest.json" }),
|
||||
NullLogger<LanguageModelService>.Instance);
|
||||
|
||||
// Sans manifeste ni fichier, le service doit retourner null (pas de throw),
|
||||
// ce qui prouve qu'il a bien utilisé un dossier de repli déterministe.
|
||||
var manifest = await service.GetManifestAsync();
|
||||
var fichier = await service.ResolveFileAsync("es", "versFrancais", "encoder.onnx");
|
||||
|
||||
Assert.Null(manifest);
|
||||
Assert.Null(fichier);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveFile_Introuvable_RetourneNull()
|
||||
{
|
||||
string root = CreateRoot();
|
||||
try
|
||||
{
|
||||
var service = CreateService(root);
|
||||
|
||||
var result = await service.ResolveFileAsync("es", "versFrancais", "absent.onnx");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveFile_CodeLangueTraversal_RetourneNull()
|
||||
{
|
||||
string root = CreateRoot();
|
||||
try
|
||||
{
|
||||
var service = CreateService(root);
|
||||
|
||||
// Segment « .. » ou contenant un séparateur → rejeté avant toute résolution.
|
||||
var result = await service.ResolveFileAsync("..", "versFrancais", "encoder.onnx");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveFile_SensInvalide_RetourneNull()
|
||||
{
|
||||
string root = CreateRoot();
|
||||
try
|
||||
{
|
||||
var service = CreateService(root);
|
||||
|
||||
var result = await service.ResolveFileAsync("es", "../depuisFrancais", "encoder.onnx");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveFile_FichierHorsRacine_RetourneNull()
|
||||
{
|
||||
string root = CreateRoot();
|
||||
string sibling = CreateRoot();
|
||||
try
|
||||
{
|
||||
await File.WriteAllTextAsync(Path.Combine(sibling, "secret.txt"), "secret");
|
||||
var service = CreateService(root);
|
||||
|
||||
// Un nom de fichier portant des segments « .. » est réduit au nom seul :
|
||||
// il ne peut jamais remonter hors de la racine configurée.
|
||||
var result = await service.ResolveFileAsync("es", "versFrancais", "..\\..\\secret.txt");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(root, true);
|
||||
Directory.Delete(sibling, true);
|
||||
}
|
||||
}
|
||||
|
||||
private static string CreateRoot()
|
||||
{
|
||||
string root = Path.Combine(Path.GetTempPath(), "omika-langues-" + Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
private static LanguageModelService CreateService(string root)
|
||||
=> new(
|
||||
Options.Create(new LanguageModelOptions { RootPath = root, ManifestFileName = "manifest.json" }),
|
||||
NullLogger<LanguageModelService>.Instance);
|
||||
}
|
||||
Reference in New Issue
Block a user