using MaBibli.Api.Services.Isbn;
using MaBibli.Shared.Dtos;
using MaBibli.Shared.Isbn;
using Microsoft.Extensions.Logging.Abstractions;
namespace MaBibli.Tests;
///
/// Codes-barres de périodiques (préfixe 977) : reconnaissance, dérivation de l'ISSN,
/// et lecture de la notice BnF correspondante.
///
public class CodePeriodiqueTests
{
///
/// Le cas remonté en usage réel : ce code renvoyait « aucun résultat », alors qu'il
/// n'a jamais pu décrire un livre.
///
private const string EanMedor = "9772466671438";
[Theory]
[InlineData(EanMedor, true)]
[InlineData("9782070612758", false)] // Le Petit Prince : un vrai livre
[InlineData("9791234567896", false)] // préfixe 979, livre lui aussi
[InlineData("2253004227", false)] // ISBN-10 : trop court pour porter un préfixe
[InlineData("9772466671437", false)] // bon préfixe, clé EAN fausse
[InlineData(null, false)]
[InlineData("", false)]
public void Reconnait_les_codes_de_periodique(string? ean, bool attendu)
=> Assert.Equal(attendu, CodePeriodique.EstCodePeriodique(ean));
///
/// La clé de l'ISSN n'est pas dans le code-barres : elle se recalcule. Vérifié
/// contre la BnF le 2026-08-18 — l'ISSN 2466-6718 y désigne bien « Médor ».
///
[Fact]
public void Deduit_lIssn_du_code_barres()
=> Assert.Equal("2466-6718", CodePeriodique.IssnDepuis(EanMedor));
[Fact]
public void Ne_deduit_rien_dun_code_de_livre()
=> Assert.Null(CodePeriodique.IssnDepuis("9782070612758"));
///
/// Les deux chiffres qui précèdent la clé encodent la parution : deux numéros d'une même
/// revue doivent donner le même ISSN, sinon chaque magazine créerait une fiche.
///
[Fact]
public void Deux_parutions_de_la_meme_revue_donnent_le_meme_Issn()
{
var issns = new[] { "9772466671438", "9772466671391" }
.Select(CodePeriodique.IssnDepuis)
.ToList();
Assert.Equal("2466-6718", issns[0]);
Assert.Equal(issns[0], issns[1]);
}
[Fact]
public void Notice_BnF_de_periodique_est_lue_et_nettoyee()
{
var revue = BnfSruParser.ParserPeriodique(
Fixture.Lire("bnf-issn-2466-6718.xml"), "2466-6718");
Assert.NotNull(revue);
// « Médor (Bruxelles) » : le parenthésé est un qualificatif de désambiguïsation,
// pas une partie du nom écrit sur la couverture.
Assert.Equal("Médor", revue!.Titre);
Assert.Equal("2466-6718", revue.Issn);
Assert.Equal("2015", revue.Annee);
}
[Fact]
public void Reponse_sans_notice_ne_produit_pas_de_titre_invente()
{
var vide = """
0
""";
Assert.Null(BnfSruParser.ParserPeriodique(vide, "2466-6718"));
}
// ── La cascade ne doit pas partir du tout ────────────────────────────────
///
/// Client BnF simulé : compte les appels ISBN pour prouver qu'aucun n'a lieu.
///
private sealed class BnfFake(PeriodiqueDetecte? revue) : IBnfClient
{
public List IsbnInterroges { get; } = [];
public List IssnInterroges { get; } = [];
public Task<(IReadOnlyList, string?)> RechercherAsync(
string isbn, string? urlCouverture, CancellationToken ct = default)
{
IsbnInterroges.Add(isbn);
return Task.FromResult(((IReadOnlyList)[], (string?)null));
}
public Task<(ResultatBibliographie, string?)> RechercherParAuteurAsync(
string auteur, CancellationToken ct = default) =>
Task.FromResult((new ResultatBibliographie(), (string?)null));
public Task<(PeriodiqueDetecte?, string?)> RechercherPeriodiqueAsync(
string issn, CancellationToken ct = default)
{
IssnInterroges.Add(issn);
return Task.FromResult((revue, (string?)null));
}
/// Recherche par titre : couverte par ses propres tests.
public Task<(IReadOnlyList, string?)> RechercherParTitreAsync(
string titre, string? auteur, CancellationToken ct = default) =>
Task.FromResult(((IReadOnlyList)[], (string?)null));
}
private sealed class OpenLibraryFake : IOpenLibraryClient
{
public int Appels { get; private set; }
public Task<(IReadOnlyList, string?)> RechercherAsync(
string isbn, string? urlCouverture, CancellationToken ct = default)
{
Appels++;
return Task.FromResult(((IReadOnlyList)[], (string?)null));
}
}
///
/// Le point du bug : la cascade s'exécutait en entier pour finir sur « aucun résultat ».
/// Elle ne doit désormais pas partir — seul l'ISSN est interrogé.
///
[Fact]
public async Task Un_code_977_ne_declenche_aucune_recherche_de_livre()
{
var bnf = new BnfFake(new PeriodiqueDetecte { Issn = "2466-6718", Titre = "Médor" });
var ol = new OpenLibraryFake();
var service = new IsbnLookupService(bnf, ol, NullLogger.Instance);
var resultat = await service.RechercherAsync(EanMedor);
Assert.NotNull(resultat);
Assert.Empty(bnf.IsbnInterroges);
Assert.Equal(0, ol.Appels);
Assert.Equal(["2466-6718"], bnf.IssnInterroges);
Assert.Empty(resultat!.Candidats);
Assert.Equal("Médor", resultat.Periodique?.Titre);
}
///
/// BnF muette : on connaît quand même l'ISSN, puisqu'il se déduit du code-barres.
/// Ne rien renvoyer ferait retomber l'écran sur « aucun résultat », le bug d'origine.
///
[Fact]
public async Task BnF_muette_laisse_quand_meme_lIssn()
{
var service = new IsbnLookupService(
new BnfFake(null), new OpenLibraryFake(), NullLogger.Instance);
var resultat = await service.RechercherAsync(EanMedor);
Assert.Equal("2466-6718", resultat?.Periodique?.Issn);
Assert.Null(resultat?.Periodique?.Titre);
}
/// Un livre ordinaire n'est jamais pris pour un périodique.
[Fact]
public async Task Un_ISBN_de_livre_passe_par_la_cascade_normale()
{
var bnf = new BnfFake(null);
var service = new IsbnLookupService(
bnf, new OpenLibraryFake(), NullLogger.Instance);
var resultat = await service.RechercherAsync("9782070612758");
Assert.Null(resultat?.Periodique);
Assert.Empty(bnf.IssnInterroges);
Assert.NotEmpty(bnf.IsbnInterroges);
}
}