using MaBibli.Api.Services.Catalogue; using MaBibli.Api.Services.Isbn; namespace MaBibli.Tests; /// /// Lecture des réponses SRU « par auteur », sur des réponses réelles enregistrées le /// 2026-08-18 (25 notices chacune). Aucun accès réseau. /// /// /// Les deux fixtures sont choisies pour ce qu'elles ont de gênant : /// /// werber : bibliographie propre mais très redondante (rééditions en série) ; /// zola : bibliographie polluée — l'index auteur de la BnF y mêle les /// ouvrages de sa fille Denise Le Blond-Zola, qui parlent de lui sans qu'il les ait écrits. /// /// public class BnfBibliographieParserTests { private const string Werber = "bnf-auteur-werber.xml"; private const string Zola = "bnf-auteur-zola.xml"; [Fact] public void Lit_le_nombre_de_notices_annonce_par_la_BnF() { var resultat = BnfBibliographieParser.Parser(Fixture.Lire(Werber), "Bernard Werber"); // La BnF en annonce bien plus qu'elle n'en renvoie : c'est ce qui permet à l'écran de // dire honnêtement qu'il ne montre qu'un extrait. Assert.True( resultat.NombreAnnonce > resultat.NombreLu, $"annoncé {resultat.NombreAnnonce}, lu {resultat.NombreLu}"); } [Fact] public void Retient_les_notices_de_l_auteur_demande() { var resultat = BnfBibliographieParser.Parser(Fixture.Lire(Werber), "Bernard Werber"); Assert.NotEmpty(resultat.Notices); Assert.All( resultat.Notices, n => Assert.Contains(n.Auteurs, a => a.Contains("Werber", StringComparison.OrdinalIgnoreCase))); } /// /// Le filtre critique : bib.author all "Émile Zola" rapproche « émile » et « zola » /// sur l'ensemble des auteurs d'une notice, et remonte donc l'œuvre de sa fille. /// [Fact] public void Ecarte_les_ouvrages_ecrits_par_quelqu_un_d_autre() { var resultat = BnfBibliographieParser.Parser(Fixture.Lire(Zola), "Émile Zola"); Assert.True( resultat.EcarteesAutreAuteur > 0, "la fixture Zola doit contenir des notices d'un autre auteur, sinon elle ne teste rien"); // Denise Le Blond-Zola a écrit sur son père ; ses livres ne sont pas de lui. Assert.DoesNotContain( resultat.Notices, n => n.Auteurs.Any(a => a.Contains("Le Blond", StringComparison.OrdinalIgnoreCase))); Assert.All( resultat.Notices, n => Assert.Contains( n.Auteurs, a => MaBibli.Shared.Textes.RapprochementAuteurs.SontLeMemeAuteur(a, "Émile Zola"))); } [Fact] public void Tolere_la_forme_bibliographique_du_nom() { // La notice porte « Werber, Bernard (1961-....). Auteur du texte » : c'est le nettoyage // ISBD puis le rapprochement d'auteurs qui doivent le ramener à « Bernard Werber ». var resultat = BnfBibliographieParser.Parser(Fixture.Lire(Werber), "werber, bernard"); Assert.NotEmpty(resultat.Notices); } [Fact] public void Extrait_l_ISBN_du_champ_identifier() { var resultat = BnfBibliographieParser.Parser(Fixture.Lire(Werber), "Bernard Werber"); var avecIsbn = resultat.Notices.Where(n => !string.IsNullOrWhiteSpace(n.Isbn)).ToList(); Assert.NotEmpty(avecIsbn); // Le préfixe « ISBN » du dc:identifier ne doit pas rester collé au numéro. Assert.All(avecIsbn, n => Assert.DoesNotContain("ISBN", n.Isbn!, StringComparison.OrdinalIgnoreCase)); Assert.All(avecIsbn, n => Assert.True(n.Isbn!.Length is 10 or 13, n.Isbn)); } [Fact] public void Coupe_la_mention_de_responsabilite_du_titre() { var resultat = BnfBibliographieParser.Parser(Fixture.Lire(Werber), "Bernard Werber"); // « Troisième humanité / Bernard Werber » → « Troisième humanité ». Assert.All(resultat.Notices, n => Assert.DoesNotContain(" / ", n.Titre)); } /// /// Le regroupement est ce qui rend l'écran utilisable : sans lui, « Les fourmis » occupe /// autant de lignes qu'il y a eu de retirages. /// [Fact] public void Regroupe_les_reeditions_sous_une_seule_oeuvre() { var resultat = BnfBibliographieParser.Parser(Fixture.Lire(Werber), "Bernard Werber"); var oeuvres = ServiceBibliographie.Regrouper(resultat).ToList(); Assert.True( oeuvres.Count < resultat.Notices.Count, $"{resultat.Notices.Count} notices devraient donner moins de {resultat.Notices.Count} œuvres"); // Aucune œuvre en double après regroupement. var cles = oeuvres.Select(o => MaBibli.Shared.Textes.CleOeuvre.Cle(o.Titre)).ToList(); Assert.Equal(cles.Count, cles.Distinct().Count()); // Au moins une œuvre a bien réuni plusieurs notices, sinon le test ne prouve rien. Assert.Contains(oeuvres, o => o.NombreEditions > 1); } [Fact] public void Retient_l_edition_la_plus_ancienne_comme_representant() { var resultat = BnfBibliographieParser.Parser(Fixture.Lire(Werber), "Bernard Werber"); foreach (var oeuvre in ServiceBibliographie.Regrouper(resultat).Where(o => o.NombreEditions > 1)) { var cle = MaBibli.Shared.Textes.CleOeuvre.Cle(oeuvre.Titre); var anneesDuGroupe = resultat.Notices .Where(n => n.CleTitre == cle) .Select(n => ServiceBibliographie.AnneeTriable(n.Annee)) .Where(a => a is not null) .Select(a => a!.Value) .ToList(); if (anneesDuGroupe.Count == 0) { continue; } Assert.Equal(anneesDuGroupe.Min(), ServiceBibliographie.AnneeTriable(oeuvre.Annee)); } } [Fact] public void Une_reponse_vide_ne_fait_pas_echouer_la_lecture() { const string vide = """ 0 """; var resultat = BnfBibliographieParser.Parser(vide, "Personne"); Assert.Empty(resultat.Notices); Assert.Equal(0, resultat.NombreAnnonce); } [Theory] [InlineData("2012", 2012)] [InlineData("[DL 2012]", 2012)] [InlineData("cop. 1991", 1991)] [InlineData("19..?", null)] [InlineData("", null)] [InlineData(null, null)] public void Extrait_une_annee_triable_des_dates_BnF(string? date, int? attendu) => Assert.Equal(attendu, ServiceBibliographie.AnneeTriable(date)); }