Ajouter la liste d'envies personnelle, son export et la bibliographie par auteur
La liste d'envies vit dans une table séparée (LivreSouhaite) plutôt que dans un statut de plus sur Livre : un livre souhaité n'est pas possédé, et le loger dans Livres l'aurait fait entrer dans le catalogue, les compteurs et les prêts, au prix d'un « et qui n'est pas souhaité » à répéter dans chaque lecture. La portée est personnelle, comme le statut de lecture — mais ici Utilisateur est une vraie frontière : toute lecture filtre dessus. Bibliographie : SRU BnF interrogé par bib.author, vérifié le 2026-08-18. Deux filtres mesurés sur des réponses réelles sont indispensables — le type de document (l'index mêle livres audio, jeux et spectacles) et surtout l'auteur réel de la notice, « all » rapprochant les mots sur l'ensemble des auteurs : « Émile Zola » remonte sinon toute l'œuvre de sa fille Denise Le Blond-Zola. Les rééditions sont regroupées par clé d'œuvre (183 notices Werber -> 51 œuvres). Le rapprochement avec l'étagère se fait par titre, pas par ISBN, qui désigne une édition et non une œuvre ; ses limites sont dites à l'écran. Export en deux formats, tous deux du texte sans dépendance : .txt groupé par auteur pour la librairie, .csv à séparateur point-virgule et BOM pour le tableur. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
using MaBibli.Api.Services.Catalogue;
|
||||
using MaBibli.Api.Services.Isbn;
|
||||
|
||||
namespace MaBibli.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Lecture des réponses SRU « par auteur », sur des réponses <b>réelles</b> enregistrées le
|
||||
/// 2026-08-18 (25 notices chacune). Aucun accès réseau.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Les deux fixtures sont choisies pour ce qu'elles ont de gênant :
|
||||
/// <list type="bullet">
|
||||
/// <item><c>werber</c> : bibliographie propre mais très redondante (rééditions en série) ;</item>
|
||||
/// <item><c>zola</c> : bibliographie <b>polluée</b> — 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.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
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)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Le filtre critique : <c>bib.author all "Émile Zola"</c> rapproche « émile » et « zola »
|
||||
/// sur l'ensemble des auteurs d'une notice, et remonte donc l'œuvre de sa fille.
|
||||
/// </summary>
|
||||
[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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Le regroupement est ce qui rend l'écran utilisable : sans lui, « Les fourmis » occupe
|
||||
/// autant de lignes qu'il y a eu de retirages.
|
||||
/// </summary>
|
||||
[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 = """
|
||||
<srw:searchRetrieveResponse xmlns:srw="http://www.loc.gov/zing/srw/">
|
||||
<srw:numberOfRecords>0</srw:numberOfRecords>
|
||||
</srw:searchRetrieveResponse>
|
||||
""";
|
||||
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user