Files
mabibli/MaBibli.Tests/IsbnScannerTests.cs
T
mathieuandClaude Opus 5 f7ad9582f6 Ajouter le scan camera du code-barres ISBN
Le decodage EAN-13/EAN-8 est fait en C# par ZXing.Net (IsbnScanner), conformement
a la decision actee dans CLAUDE.md : le JavaScript ne fournit que les pixels de la
camera, aucune logique de decodage ne lui est confiee.

Le scan se greffe en amont du flux d'ajout par ISBN existant : des qu'un code est
lu et reconnu comme ISBN-13 valide, la recherche de notices est declenchee sans
que l'utilisateur ait a retaper quoi que ce soit. La saisie manuelle reste
accessible en permanence, y compris quand la camera echoue.

Points traites explicitement :
- camera arriere par defaut (facingMode "environment", en "ideal" pour ne pas
  echouer sur un PC sans camera arriere) ;
- liberation de la camera a l'annulation, au succes et au demontage du composant ;
- cadence limitee a ~12 images/s et decodage restreint a la bande centrale
  reduite a 640 px de large : inutile de decoder chaque frame en pleine taille ;
- messages distincts pour permission refusee, absence de camera, camera occupee
  et contexte non securise (getUserMedia exige HTTPS ou localhost : une IP de
  reseau local en http ne marchera jamais, c'est une source de confusion garantie).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 23:32:05 +02:00

103 lines
3.5 KiB
C#

using MaBibli.Client.Scan;
using ZXing.Common;
using ZXing.OneD;
namespace MaBibli.Tests;
/// <summary>
/// Tests du décodage pur (aucune caméra, aucun navigateur) : on encode un EAN-13 avec
/// ZXing, on le convertit en buffer RGBA comme le ferait <c>getImageData</c>, et on
/// vérifie le trajet aller-retour. Aucune image externe n'est nécessaire.
/// </summary>
public class IsbnScannerTests
{
// Les ISBN de vérification du projet (voir CLAUDE.md).
[Theory]
[InlineData("9782070612758")] // Le Petit Prince
[InlineData("9782253004226")] // Germinal
[InlineData("9782080704092")] // Le Horla
[InlineData("9780262033848")] // Introduction to Algorithms
public void Decode_un_EAN13_genere(string isbn)
{
var (rgba, largeur, hauteur) = ImageEan13(isbn);
Assert.Equal(isbn, IsbnScanner.TryDecode(rgba, largeur, hauteur));
}
[Fact]
public void Decode_aussi_un_EAN8()
{
// Pas un ISBN, mais le format est dans les hints : le décodeur doit le lire,
// c'est la couche appelante qui écarte les codes non-ISBN.
var matrice = new EAN8Writer().encode("96385074", ZXing.BarcodeFormat.EAN_8, 260, 160);
var (rgba, largeur, hauteur) = VersRgba(matrice);
Assert.Equal("96385074", IsbnScanner.TryDecode(rgba, largeur, hauteur));
}
[Fact]
public void Renvoie_null_sur_une_frame_sans_code_barres()
{
// Cas très majoritaire en usage réel : la caméra filme autre chose.
// ZXing doit rendre null, jamais lever d'exception.
const int largeur = 640, hauteur = 480;
var rgba = new byte[largeur * hauteur * 4];
var alea = new Random(1234);
alea.NextBytes(rgba);
Assert.Null(IsbnScanner.TryDecode(rgba, largeur, hauteur));
}
[Fact]
public void Renvoie_null_sur_une_image_uniforme()
{
var rgba = new byte[320 * 200 * 4];
Array.Fill(rgba, (byte)255);
Assert.Null(IsbnScanner.TryDecode(rgba, 320, 200));
}
[Theory]
[InlineData(0, 100)]
[InlineData(100, 0)]
[InlineData(-5, 100)]
public void Renvoie_null_sur_des_dimensions_absurdes(int largeur, int hauteur)
=> Assert.Null(IsbnScanner.TryDecode(new byte[4000], largeur, hauteur));
[Fact]
public void Renvoie_null_si_le_buffer_est_trop_court()
{
// Une frame tronquée ne doit pas faire tomber la boucle de scan.
Assert.Null(IsbnScanner.TryDecode(new byte[100], 640, 480));
}
private static (byte[] Rgba, int Largeur, int Hauteur) ImageEan13(string isbn)
=> VersRgba(new EAN13Writer().encode(isbn, ZXing.BarcodeFormat.EAN_13, 380, 160));
/// <summary>
/// Convertit une BitMatrix en buffer RGBA, exactement la forme que le canvas
/// du navigateur fournit à C# (4 octets par pixel, opaque).
/// </summary>
private static (byte[] Rgba, int Largeur, int Hauteur) VersRgba(BitMatrix matrice)
{
var largeur = matrice.Width;
var hauteur = matrice.Height;
var rgba = new byte[largeur * hauteur * 4];
for (var y = 0; y < hauteur; y++)
{
for (var x = 0; x < largeur; x++)
{
var valeur = matrice[x, y] ? (byte)0 : (byte)255;
var i = ((y * largeur) + x) * 4;
rgba[i] = valeur;
rgba[i + 1] = valeur;
rgba[i + 2] = valeur;
rgba[i + 3] = 255;
}
}
return (rgba, largeur, hauteur);
}
}