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); } }