Files
mabibli/MaBibli.Tests/BnfSruParserTests.cs
T
Mathieu LimonierandClaude Opus 5 6a6d745af4 MaBibli 1.0.0
Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts,
scan de code-barres, consultation hors-ligne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 22:36:16 +02:00

182 lines
7.6 KiB
C#

using MaBibli.Api.Services.Isbn;
using MaBibli.Shared.Dtos;
namespace MaBibli.Tests;
/// <summary>
/// Parsing de réponses SRU réelles de la BnF (schéma <c>dublincore</c>).
/// </summary>
public class BnfSruParserTests
{
[Theory]
// Le piège bloquant, mesuré : ces trois ISBN-13 donnent 0 notice, leur ISBN-10 en donne.
[InlineData("bnf-9782070612758.xml", 1)] // Le Petit Prince (2007) : trouvé en ISBN-13
[InlineData("bnf-9782253004226.xml", 0)] // Germinal en ISBN-13 : rien
[InlineData("bnf-2253004227.xml", 3)] // Germinal en ISBN-10 : 3 rééditions
[InlineData("bnf-9782080704092.xml", 0)] // Le Horla en ISBN-13 : rien
[InlineData("bnf-2080704095.xml", 1)] // Le Horla en ISBN-10 : 1 notice
[InlineData("bnf-9780262033848.xml", 0)] // Livre étranger : absent du dépôt légal français
[InlineData("bnf-0262033844.xml", 0)] // …sous les deux formes d'ISBN
public void NombreDeNotices_correspond_aux_mesures(string fixture, int attendu)
=> Assert.Equal(attendu, BnfSruParser.LireNombreDeNotices(Fixture.Lire(fixture)));
[Fact]
public void Petit_Prince_est_parse_et_nettoye()
{
var candidats = BnfSruParser.Parser(
Fixture.Lire("bnf-9782070612758.xml"), "9782070612758", "https://couverture");
var c = Assert.Single(candidats);
Assert.Equal("Le petit prince", c.Titre);
Assert.Equal("Antoine de Saint-Exupéry", c.Auteur);
Assert.Equal("Gallimard", c.Editeur);
Assert.Equal("2007", c.Annee);
Assert.Equal("fre", c.Langue);
Assert.Equal(SourceMetadonnees.Bnf, c.Source);
Assert.Equal("9782070612758", c.IsbnInterroge);
Assert.Equal("https://couverture", c.CoverUrl);
Assert.StartsWith("ark:/12148/", c.IdentifiantSource);
Assert.Equal($"https://catalogue.bnf.fr/{c.IdentifiantSource}", c.UrlNotice);
}
/// <summary>
/// Le nombre de pages est lu dans <c>dc:format</c>, qui décrit le support en toutes lettres.
/// </summary>
/// <remarks>
/// ⚠️ Il est <b>proposé</b>, jamais imposé : la valeur traverse le candidat jusqu'à un champ
/// modifiable. Les deux formes du catalogue sont représentées ici — « 1 vol. (113 p.) … »
/// et « 503 p. … » —, et les dimensions qui les suivent (« ; 18 cm ») ne doivent surtout pas
/// être prises pour une pagination.
/// </remarks>
[Fact]
public void Le_nombre_de_pages_est_lu_dans_la_description_materielle()
{
var petitPrince = BnfSruParser.Parser(
Fixture.Lire("bnf-9782070612758.xml"), "9782070612758", null);
Assert.Equal(113, Assert.Single(petitPrince).NombrePages);
var germinal = BnfSruParser.Parser(
Fixture.Lire("bnf-2253004227.xml"), "2253004227", null);
Assert.Equal([503, 605, 538], germinal.Select(c => c.NombrePages));
}
[Fact]
public void Germinal_renvoie_les_trois_reeditions_distinctes()
{
// Décision actée : on ne choisit pas à la place de l'utilisateur, on remonte tout.
var candidats = BnfSruParser.Parser(
Fixture.Lire("bnf-2253004227.xml"), "2253004227", null);
Assert.Equal(3, candidats.Count);
Assert.All(candidats, c => Assert.Equal("Germinal", c.Titre));
Assert.All(candidats, c => Assert.Equal("Émile Zola", c.Auteur));
Assert.Equal(
["le Livre de poche", "Librairie générale française", "Librairie générale française"],
candidats.Select(c => c.Editeur));
Assert.Equal(["1979", "2000", "1983"], candidats.Select(c => c.Annee));
// Les notices doivent rester discernables : sans ark distinct l'utilisateur ne peut pas trancher.
Assert.Equal(3, candidats.Select(c => c.IdentifiantSource).Distinct().Count());
}
[Fact]
public void Le_Horla_est_parse_depuis_lisbn10()
{
var candidats = BnfSruParser.Parser(
Fixture.Lire("bnf-2080704095.xml"), "2080704095", null);
var c = Assert.Single(candidats);
// La BnF écrit « Le horla » : le nettoyage ne doit pas « corriger » la casse de la source.
Assert.Equal("Le horla", c.Titre);
Assert.Equal("Guy de Maupassant", c.Auteur);
Assert.Equal("2080704095", c.IsbnInterroge);
Assert.DoesNotContain("(", c.Editeur ?? string.Empty);
}
/// <summary>
/// Notice minimale bâtie à la main, au format de celles de la BnF.
/// </summary>
/// <remarks>
/// Les cas ci-dessous n'ont pas de réponse réelle enregistrée : ils portent sur la manière
/// dont le parseur <b>combine</b> ses règles, pas sur la lecture du XML — que les fixtures
/// réelles couvrent déjà.
/// </remarks>
private static string Notice(string titre, params string[] auteurs) =>
$"""
<srw:searchRetrieveResponse xmlns:srw="http://www.loc.gov/zing/srw/">
<srw:numberOfRecords>1</srw:numberOfRecords>
<srw:records><srw:record>
<srw:recordData xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>{titre}</dc:title>
{string.Concat(auteurs.Select(a => $"<dc:creator>{a}</dc:creator>"))}
</srw:recordData>
</srw:record></srw:records>
</srw:searchRetrieveResponse>
""";
/// <summary>
/// Cas réel de l'ISBN <c>9782749942636</c> : la BnF recolle le nom de l'auteur en fin de
/// titre <b>sans</b> le séparateur ISBD. Il faut donc les auteurs nettoyés avant de pouvoir
/// finir le titre — c'est l'ordre des opérations qui est vérifié ici.
/// </summary>
[Fact]
public void Le_nom_de_lauteur_colle_en_fin_de_titre_est_retire()
{
var candidats = BnfSruParser.Parser(
Notice(
"Le plus grand défi de l'histoire de l'humanité : face à la catastrophe "
+ "écologique et sociale (Nouvelle éd. augmentée) Aurélien Barrau",
"Barrau, Aurélien (1973-....). Auteur du texte"),
"9782749942636",
null);
var c = Assert.Single(candidats);
Assert.Equal(
"Le plus grand défi de l'histoire de l'humanité : face à la catastrophe écologique "
+ "et sociale (Nouvelle éd. augmentée)",
c.Titre);
Assert.Equal(["Aurélien Barrau"], c.Auteurs);
}
/// <summary>Contre-exemple : un nom de personne ailleurs que tout à la fin reste en place.</summary>
[Fact]
public void Un_nom_de_personne_dans_le_titre_nest_pas_mutile()
{
var candidats = BnfSruParser.Parser(
Notice("Lettre à Émile Zola et autres textes", "Maupassant, Guy de (1850-1893)"),
"0000000000",
null);
Assert.Equal("Lettre à Émile Zola et autres textes", Assert.Single(candidats).Titre);
}
[Fact]
public void Plusieurs_dc_creator_donnent_plusieurs_auteurs()
{
var candidats = BnfSruParser.Parser(
Notice(
"Le Horla et autres nouvelles",
"Maupassant, Guy de (1850-1893). Auteur du texte",
"Zola, Émile (1840-1902). Préfacier"),
"0000000000",
null);
var c = Assert.Single(candidats);
Assert.Equal(["Guy de Maupassant", "Émile Zola"], c.Auteurs);
// La forme « une ligne » reste disponible pour un champ de saisie unique.
Assert.Equal("Guy de Maupassant; Émile Zola", c.Auteur);
}
[Fact]
public void Reponse_sans_notice_donne_une_liste_vide_et_pas_une_exception()
{
var candidats = BnfSruParser.Parser(
Fixture.Lire("bnf-9780262033848.xml"), "9780262033848", null);
Assert.Empty(candidats);
}
}