Files
2026-10-23 18:46:50 +02:00

190 lines
7.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Omika.App.Services;
using Omika.App.ViewModels;
using Omika.Core.Constants;
using SkiaSharp;
namespace Omika.Tests.Mobile.Local;
/// <summary>
/// Tests de l'orientation EXIF et des règles de la politique de poids des photos.
///
/// L'orientation est vérifiée sur une image 2×1 dont les deux pixels sont
/// distincts, ce qui permet de valider les huit orientations sans dépendre d'un
/// fichier EXIF réel.
/// </summary>
public class ImageOrientationTests
{
private static readonly SKColor Rouge = new(255, 0, 0);
private static readonly SKColor Bleu = new(0, 0, 255);
/// <summary>Source 2×1 : pixel gauche rouge, pixel droit bleu.</summary>
private static SKBitmap CreateSource()
{
var bitmap = new SKBitmap(new SKImageInfo(2, 1, SKColorType.Rgba8888, SKAlphaType.Premul));
bitmap.SetPixel(0, 0, Rouge);
bitmap.SetPixel(1, 0, Bleu);
return bitmap;
}
private static SKColor Pixel(SKBitmap bitmap, int x, int y) => bitmap.GetPixel(x, y);
[Fact]
public void TopLeft_AucuneTransformation()
{
// L'orientation normale ne doit produire aucune copie inutile.
using SKBitmap source = CreateSource();
Assert.Null(ImageOrientation.ApplyOrientation(source, SKEncodedOrigin.TopLeft));
}
[Theory]
// Miroir horizontal : [R B] → [B R]
[InlineData(SKEncodedOrigin.TopRight, 2, 1, 1, 0, true)]
// Rotation 180° : [R B] → [B R]
[InlineData(SKEncodedOrigin.BottomRight, 2, 1, 1, 0, true)]
// Miroir vertical sur une seule ligne : identité [R B]
[InlineData(SKEncodedOrigin.BottomLeft, 2, 1, 0, 0, true)]
// Transposition : colonne [R ; B]
[InlineData(SKEncodedOrigin.LeftTop, 1, 2, 0, 0, true)]
// Rotation 90° horaire : colonne [R ; B]
[InlineData(SKEncodedOrigin.RightTop, 1, 2, 0, 0, true)]
// Miroir + 90° horaire : colonne [B ; R]
[InlineData(SKEncodedOrigin.RightBottom, 1, 2, 0, 0, false)]
// Rotation 90° anti-horaire : colonne [B ; R]
[InlineData(SKEncodedOrigin.LeftBottom, 1, 2, 0, 0, false)]
public void Orientation_AxesEtCouleursCorrects(
SKEncodedOrigin origin,
int expectedWidth,
int expectedHeight,
int xDuPremierPixel,
int yDuPremierPixel,
bool premierPixelRouge)
{
// Act
using SKBitmap source = CreateSource();
using SKBitmap? result = ImageOrientation.ApplyOrientation(source, origin);
// Assert : dimensions permutées quand l'orientation le requiert…
Assert.NotNull(result);
Assert.Equal(expectedWidth, result.Width);
Assert.Equal(expectedHeight, result.Height);
// … et orientation réellement appliquée (aucun pixel perdu, aucun mélange).
SKColor premier = Pixel(result, xDuPremierPixel, yDuPremierPixel);
SKColor attendu = premierPixelRouge ? Rouge : Bleu;
Assert.Equal(attendu, premier);
// Les deux couleurs d'origine sont toujours présentes exactement une fois.
int rouges = 0;
int bleus = 0;
for (int x = 0; x < result.Width; x++)
{
for (int y = 0; y < result.Height; y++)
{
SKColor pixel = Pixel(result, x, y);
if (pixel == Rouge) rouges++;
if (pixel == Bleu) bleus++;
}
}
Assert.Equal(1, rouges);
Assert.Equal(1, bleus);
}
}
/// <summary>
/// Tests des règles de la politique de poids des photos : détection d'image,
/// normalisation de la valeur configurée et formatage lisible.
/// </summary>
public class PhotoOptimizationSettingsTests
{
[Theory]
[InlineData("photo.jpg", true)]
[InlineData("photo.JPEG", true)]
[InlineData("capture.png", true)]
[InlineData("anim.gif", true)]
[InlineData("image.webp", true)]
[InlineData("photo.heic", true)]
[InlineData("scan.tiff", true)]
[InlineData("rapport.pdf", false)]
[InlineData("lettre.docx", false)]
[InlineData("classeur.xlsx", false)]
[InlineData("archive.zip", false)]
[InlineData("notes.txt", false)]
[InlineData("sans_extension", false)]
[InlineData("", false)]
public void IsImageExtension_ClasseCorrectement(string fileName, bool expected)
=> Assert.Equal(expected, ImageFileTypes.IsImageExtension(fileName));
[Fact]
public void IsImage_MimeSuffitSansExtension()
=> Assert.True(ImageFileTypes.IsImage("fichier", "image/jpeg"));
[Fact]
public void IsImage_ExtensionSuffitSansMime()
=> Assert.True(ImageFileTypes.IsImage("photo.jpg", null));
[Fact]
public void IsImage_PdfJamaisImage()
=> Assert.False(ImageFileTypes.IsImage("rapport.pdf", "application/pdf"));
[Theory]
[InlineData(null, 500, PhotoLimitSource.DefaultFallback)]
[InlineData("", 500, PhotoLimitSource.DefaultFallback)]
[InlineData(" ", 500, PhotoLimitSource.DefaultFallback)]
[InlineData("abc", 500, PhotoLimitSource.DefaultFallback)]
[InlineData("-10", 500, PhotoLimitSource.DefaultFallback)]
// 0 est une valeur métier valide : « aucune limite », jamais remplacée par le repli.
[InlineData("0", 0, PhotoLimitSource.ApplicationParameter)]
[InlineData("500", 500, PhotoLimitSource.ApplicationParameter)]
[InlineData(" 2048 ", 2048, PhotoLimitSource.ApplicationParameter)]
[InlineData("10240", 10240, PhotoLimitSource.ApplicationParameter)]
public void Resolve_ValeurEtSource(string? valeur, int expectedLimit, PhotoLimitSource expectedSource)
{
PhotoLimitResolution resolution = PhotoOptimizationSettings.Resolve(valeur);
Assert.Equal(expectedLimit, resolution.LimitKb);
Assert.Equal(expectedSource, resolution.Source);
Assert.Equal(expectedSource == PhotoLimitSource.DefaultFallback, resolution.IsFallback);
Assert.Equal(expectedLimit <= 0, resolution.IsDisabled);
}
[Fact]
public void Resolve_Zero_NEStJamaisRemplaceParLeRepli()
{
// Règle métier critique : 0 ≠ absent.
PhotoLimitResolution zero = PhotoOptimizationSettings.Resolve("0");
PhotoLimitResolution absent = PhotoOptimizationSettings.Resolve(null);
Assert.Equal(0, zero.LimitKb);
Assert.True(zero.IsDisabled);
Assert.False(zero.IsFallback);
Assert.Equal(500, absent.LimitKb);
Assert.True(absent.IsFallback);
Assert.NotEqual(zero.LimitKb, absent.LimitKb);
}
[Theory]
[InlineData(0, "Aucune limite")]
[InlineData(250, "250 Ko")]
[InlineData(500, "500 Ko")]
[InlineData(750, "750 Ko")]
[InlineData(1024, "1 Mo")]
[InlineData(1536, "1,5 Mo")]
[InlineData(2048, "2 Mo")]
[InlineData(7680, "7,5 Mo")]
[InlineData(10240, "10 Mo")]
public void FormatPhotoSize_LibelleUtilisateur(int kiloOctets, string expected)
=> Assert.Equal(expected, ParametresViewModel.FormatPhotoSize(kiloOctets));
[Theory]
[InlineData(500, 2)]
[InlineData(2, 0)]
[InlineData(0, 0)]
[InlineData(10240, 11)]
[InlineData(9000, 11)]
[InlineData(1100, 4)]
public void IndexDuPalierLePlusProche_PositionneLaJauge(int kiloOctets, int expectedIndex)
=> Assert.Equal(expectedIndex, ParametresViewModel.IndexDuPalierLePlusProche(kiloOctets));
}