using MaBibli.Api.Data;
using MaBibli.Api.Services.Catalogue;
using MaBibli.Shared.Catalogue;
using MaBibli.Shared.Dtos;
using MaBibli.Shared.Entites;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace MaBibli.Tests;
///
/// Table des auteurs : regroupement automatique, suggestions et fusions.
///
public class ServiceAuteursTests : IDisposable
{
private readonly SqliteConnection _connexion;
private readonly MaBibliDbContext _db;
private readonly ServiceAuteurs _auteurs;
private readonly ServiceCatalogue _catalogue;
public ServiceAuteursTests()
{
_connexion = new SqliteConnection("Data Source=:memory:");
_connexion.Open();
_db = new MaBibliDbContext(
new DbContextOptionsBuilder().UseSqlite(_connexion).Options);
_db.Database.EnsureCreated();
_auteurs = new ServiceAuteurs(_db);
_catalogue = new ServiceCatalogue(_db, _auteurs);
}
private async Task AjouterAsync(string titre, string auteurs) =>
(await _catalogue.CreerAsync(
new EnregistrementLivre { Titre = titre, Auteur = auteurs }, "mathieu")).Livre!;
[Fact]
public async Task Un_auteur_nest_cree_quune_fois()
{
await AjouterAsync("Germinal", "Émile Zola");
await AjouterAsync("Nana", "Émile Zola");
var liste = await _auteurs.ListerAsync();
var zola = Assert.Single(liste);
Assert.Equal("Émile Zola", zola.Nom);
Assert.Equal(2, zola.NombreDeLivres);
}
[Fact]
public async Task Le_nom_daffichage_reste_celui_de_la_premiere_saisie()
{
await AjouterAsync("Germinal", "Émile Zola");
await AjouterAsync("Nana", "ZOLA, emile");
// Les deux livres pointent la même fiche, sous le nom d'origine : l'accentuation correcte
// ne doit pas être écrasée par une saisie plus négligée.
Assert.Equal("Émile Zola", Assert.Single(await _auteurs.ListerAsync()).Nom);
}
[Fact]
public async Task Aucune_suggestion_sur_des_auteurs_sans_rapport()
{
await AjouterAsync("Germinal", "Émile Zola");
await AjouterAsync("Le Horla", "Guy de Maupassant");
Assert.Empty(await _auteurs.SuggestionsAsync());
}
[Fact]
public async Task Un_patronyme_seul_est_propose_vers_le_nom_complet()
{
await AjouterAsync("Pandora's Star", "Peter F. Hamilton");
await AjouterAsync("Un autre", "Hamilton");
var suggestion = Assert.Single(await _auteurs.SuggestionsAsync());
// La fusion efface la source : c'est le nom le moins complet qui doit disparaître.
Assert.Equal("Hamilton", suggestion.Source.Nom);
Assert.Equal("Peter F. Hamilton", suggestion.Cible.Nom);
}
[Fact]
public async Task Accepter_un_rapprochement_reunit_les_livres()
{
var complet = await AjouterAsync("Pandora's Star", "Peter F. Hamilton");
var court = await AjouterAsync("Judas Unchained", "Hamilton");
var suggestion = Assert.Single(await _auteurs.SuggestionsAsync());
Assert.True(await _auteurs.FusionnerAsync(suggestion.Source.Id, suggestion.Cible.Id));
var restant = Assert.Single(await _auteurs.ListerAsync());
Assert.Equal("Peter F. Hamilton", restant.Nom);
Assert.Equal(2, restant.NombreDeLivres);
// Les deux livres remontent maintenant sous le même auteur.
var groupe = await _catalogue.ListerAsync(new CritereLivres { AuteurId = restant.Id }, "mathieu");
Assert.Equal([complet.Id, court.Id], groupe.Select(l => l.Id).Order());
// Et la suggestion ne peut plus revenir : l'une des deux fiches n'existe plus.
Assert.Empty(await _auteurs.SuggestionsAsync());
}
[Fact]
public async Task Refuser_un_rapprochement_le_fait_taire_definitivement()
{
await AjouterAsync("Pandora's Star", "Peter F. Hamilton");
await AjouterAsync("Un autre", "Hamilton");
var suggestion = Assert.Single(await _auteurs.SuggestionsAsync());
Assert.True(await _auteurs.RefuserAsync(suggestion.Source.Id, suggestion.Cible.Id));
Assert.Empty(await _auteurs.SuggestionsAsync());
// Les deux auteurs restent bien distincts : refuser n'est pas fusionner.
Assert.Equal(2, (await _auteurs.ListerAsync()).Count);
}
[Fact]
public async Task Un_refus_vaut_dans_les_deux_sens()
{
await AjouterAsync("Pandora's Star", "Peter F. Hamilton");
await AjouterAsync("Un autre", "Hamilton");
var suggestion = Assert.Single(await _auteurs.SuggestionsAsync());
// Refus enregistré dans l'ordre inverse de celui proposé : il doit compter quand même.
Assert.True(await _auteurs.RefuserAsync(suggestion.Cible.Id, suggestion.Source.Id));
Assert.Empty(await _auteurs.SuggestionsAsync());
// Idempotent : redire non ne doit pas empiler les lignes.
Assert.True(await _auteurs.RefuserAsync(suggestion.Source.Id, suggestion.Cible.Id));
Assert.Equal(1, await _db.RapprochementsRefuses.CountAsync());
}
[Fact]
public async Task Fusionner_un_livre_cosigne_ne_cree_pas_de_lien_en_double()
{
// Le même livre signé par les deux variantes : après fusion il ne doit rester qu'un lien.
var livre = await AjouterAsync("Œuvre commune", "Peter F. Hamilton; Hamilton");
Assert.Equal(2, livre.Auteurs.Count);
var suggestion = Assert.Single(await _auteurs.SuggestionsAsync());
Assert.True(await _auteurs.FusionnerAsync(suggestion.Source.Id, suggestion.Cible.Id));
var relu = await _catalogue.ObtenirAsync(livre.Id, "mathieu");
Assert.Single(relu!.Auteurs);
Assert.Equal(1, await _db.LivreAuteurs.CountAsync());
}
[Fact]
public async Task Fusionner_avec_un_auteur_inexistant_echoue_sans_rien_casser()
{
var livre = await AjouterAsync("Germinal", "Émile Zola");
var zola = livre.Auteurs.Single().Id;
Assert.False(await _auteurs.FusionnerAsync(zola, 4242));
Assert.False(await _auteurs.FusionnerAsync(zola, zola));
Assert.Equal(1, await _db.Auteurs.CountAsync());
}
[Fact]
public async Task Retirer_un_auteur_dun_livre_supprime_sa_fiche_devenue_vide()
{
var livre = await AjouterAsync("Germinal", "Émile Zola");
await _catalogue.ModifierAsync(
livre.Id,
new EnregistrementLivre { Titre = "Germinal", Auteur = "Guy de Maupassant" },
"mathieu");
var restant = Assert.Single(await _auteurs.ListerAsync());
Assert.Equal("Guy de Maupassant", restant.Nom);
}
[Fact]
public async Task Une_saisie_sans_lettre_ne_cree_pas_dauteur()
{
await AjouterAsync("Sans auteur", " ; ; .,; ");
Assert.Empty(await _auteurs.ListerAsync());
}
[Fact]
public async Task Le_meme_auteur_saisi_deux_fois_ne_donne_quun_lien()
{
var livre = await AjouterAsync("Germinal", "Émile Zola; ZOLA, Emile");
Assert.Single(livre.Auteurs);
}
[Fact]
public async Task Renommer_un_auteur_recalcule_ses_formes()
{
var livre = await AjouterAsync("Pandora's Star", "Hamilton");
var resultat = await _auteurs.RenommerAsync(
livre.Auteurs.Single().Id, "Peter F. Hamilton");
Assert.False(resultat.EstCollision);
Assert.Equal("Peter F. Hamilton", resultat.Auteur!.Nom);
var auteur = await _db.Auteurs.SingleAsync();
Assert.Equal("peter f hamilton", auteur.NomNormalise);
Assert.Equal("f hamilton peter", auteur.CleRegroupement);
}
[Fact]
public async Task Renommer_avec_collision_demande_puis_fusionne()
{
var court = await AjouterAsync("Judas Unchained", "Hamilton");
var complet = await AjouterAsync("Pandora's Star", "Peter F. Hamilton");
var idCourt = court.Auteurs.Single().Id;
var idComplet = complet.Auteurs.Single().Id;
var avertissement = await _auteurs.RenommerAsync(idCourt, "Peter F. Hamilton");
Assert.True(avertissement.EstCollision);
Assert.Equal(idComplet, avertissement.Collision!.Id);
Assert.Equal(2, await _db.Auteurs.CountAsync());
var fusion = await _auteurs.RenommerAsync(
idCourt, "Peter F. Hamilton", confirmerFusion: true);
Assert.False(fusion.EstCollision);
Assert.Single(await _db.Auteurs.ToListAsync());
Assert.Equal(2, await _db.LivreAuteurs.CountAsync());
Assert.Equal(2, (await _auteurs.ListerAsync()).Single().NombreDeLivres);
}
public void Dispose()
{
_db.Dispose();
_connexion.Dispose();
GC.SuppressFinalize(this);
}
}