using MaBibli.Api.Data; using MaBibli.Api.Services.Souhaits; using MaBibli.Shared.Dtos; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; namespace MaBibli.Tests; /// /// Les envies de revues, sur une base SQLite en mémoire : l'unicité /// (utilisateur, titre, numéro) n'existe qu'en base. /// /// /// Mêmes invariants que la liste d'envies de livres, et ils comptent autant : la table est /// personnelle, et l'envie d'un autre doit être introuvable, jamais refusée. /// public class ServiceRevuesSouhaiteesTests : IDisposable { private readonly SqliteConnection _connexion; private readonly MaBibliDbContext _db; private readonly ServiceRevuesSouhaitees _envies; private const string Mathieu = "mathieu"; private const string Camille = "camille"; public ServiceRevuesSouhaiteesTests() { _connexion = new SqliteConnection("Data Source=:memory:"); _connexion.Open(); _db = new MaBibliDbContext( new DbContextOptionsBuilder().UseSqlite(_connexion).Options); _db.Database.EnsureCreated(); _envies = new ServiceRevuesSouhaitees(_db); } private Task AjouterAsync( string titre, string? numero = null, string? issn = null, string? note = null, string utilisateur = Mathieu) => _envies.AjouterAsync( new EnregistrementRevueSouhaitee { Titre = titre, Numero = numero, Issn = issn, Note = note, }, utilisateur); /// /// L'ISSN est le seul code du projet rangé avec son tiret : c'est la forme que /// produit le code-barres et celle qu'interroge la BnF. Sans canonisation, un ISSN tapé à /// la main ne se rapprocherait de rien. /// [Fact] public async Task LIssn_est_canonise_avec_son_tiret() { var envie = (await AjouterAsync("Médor", "43", "24666718")).Envie!; Assert.Equal("2466-6718", envie.Issn); Assert.Equal("2466-6718", _db.RevuesSouhaitees.Single().Issn); } [Fact] public async Task Un_code_qui_nest_pas_un_issn_est_conserve_tel_quel() { // Un code mal recopié doit se voir mal recopié, pas disparaître ni être déguisé. var envie = (await AjouterAsync("Médor", issn: "pas-un-issn")).Envie!; Assert.Equal("pas-un-issn", envie.Issn); } [Fact] public async Task Deux_numeros_de_la_meme_revue_cohabitent() { await AjouterAsync("Médor", "43"); var second = await AjouterAsync("Médor", "44"); Assert.Null(second.Erreur); Assert.Equal(2, _db.RevuesSouhaitees.Count()); } /// /// ⚠️ Le piège que la colonne NumeroNormalise non nullable ferme : SQLite tient deux /// NULL pour distincts, et « Médor, sans numéro » s'ajouterait autant de fois qu'on /// cliquerait. /// [Fact] public async Task La_meme_revue_sans_numero_ne_sajoute_quune_fois() { await AjouterAsync("Médor"); var second = await AjouterAsync("Médor"); Assert.NotNull(second.Erreur); Assert.Contains("déjà dans votre liste", second.Erreur); Assert.Single(_db.RevuesSouhaitees); } [Fact] public async Task La_revue_entiere_et_un_de_ses_numeros_sont_deux_envies() { await AjouterAsync("Médor"); var numero = await AjouterAsync("Médor", "43"); Assert.Null(numero.Erreur); Assert.Equal(2, _db.RevuesSouhaitees.Count()); } [Fact] public async Task Deux_personnes_peuvent_souhaiter_le_meme_numero() { await AjouterAsync("Médor", "43"); var autre = await AjouterAsync("Médor", "43", utilisateur: Camille); Assert.Null(autre.Erreur); Assert.Single(await _envies.ListerAsync(Mathieu)); Assert.Single(await _envies.ListerAsync(Camille)); } [Fact] public async Task La_liste_suit_le_rang_et_une_envie_neuve_va_en_fin() { await AjouterAsync("Médor", "43"); await AjouterAsync("XXI"); var liste = await _envies.ListerAsync(Mathieu); Assert.Equal(["Médor n° 43", "XXI"], liste.Select(e => e.TitreComplet)); Assert.Equal([0, 1], _db.RevuesSouhaitees.OrderBy(e => e.Id).Select(e => e.Rang)); } [Fact] public async Task Editer_lenvie_dun_autre_est_introuvable_et_ne_change_rien() { var sienne = (await AjouterAsync("Médor", "43", utilisateur: Camille)).Envie!; var resultat = await _envies.ModifierAsync( sienne.Id, new EnregistrementRevueSouhaitee { Titre = "Détournée" }, Mathieu); Assert.True(resultat.NExistePas); Assert.Null(resultat.Erreur); Assert.Equal("Médor", _db.RevuesSouhaitees.Single().Titre); } [Fact] public async Task Editer_recalcule_les_formes_et_garde_le_rang() { await AjouterAsync("Médor", "43"); var seconde = (await AjouterAsync("XXI")).Envie!; var resultat = await _envies.ModifierAsync( seconde.Id, new EnregistrementRevueSouhaitee { Titre = "Six Mois", Numero = "Hors-Série 7" }, Mathieu); Assert.Null(resultat.Erreur); var ligne = _db.RevuesSouhaitees.Single(e => e.Id == seconde.Id); Assert.Equal("six mois", ligne.TitreNormalise); Assert.Equal("hors serie 7", ligne.NumeroNormalise); Assert.Equal(1, ligne.Rang); } [Fact] public async Task Editer_vers_une_envie_deja_presente_est_refuse_lisiblement() { await AjouterAsync("Médor", "43"); var seconde = (await AjouterAsync("Médor", "44")).Envie!; var resultat = await _envies.ModifierAsync( seconde.Id, new EnregistrementRevueSouhaitee { Titre = "MÉDOR", Numero = "43" }, Mathieu); Assert.False(resultat.NExistePas); Assert.NotNull(resultat.Erreur); Assert.DoesNotContain("UNIQUE", resultat.Erreur, StringComparison.OrdinalIgnoreCase); Assert.Equal(2, _db.RevuesSouhaitees.Count()); } /// /// ⚠️ Sans exclusion explicite de sa propre ligne, toute édition se dirait doublon /// d'elle-même — et il serait impossible de corriger une note. /// [Fact] public async Task Reenregistrer_la_meme_envie_nest_pas_un_doublon_delle_meme() { var envie = (await AjouterAsync("Médor", "43")).Envie!; var resultat = await _envies.ModifierAsync( envie.Id, new EnregistrementRevueSouhaitee { Titre = "Médor", Numero = "43", Note = "au kiosque" }, Mathieu); Assert.Null(resultat.Erreur); Assert.Equal("au kiosque", _db.RevuesSouhaitees.Single().Note); } [Fact] public async Task Retirer_lenvie_dun_autre_est_refuse() { var sienne = (await AjouterAsync("Médor", "43", utilisateur: Camille)).Envie!; Assert.False(await _envies.SupprimerAsync(sienne.Id, Mathieu)); Assert.Single(_db.RevuesSouhaitees); Assert.True(await _envies.SupprimerAsync(sienne.Id, Camille)); Assert.Empty(_db.RevuesSouhaitees); } [Fact] public async Task Sans_identite_il_ny_a_ni_liste_ni_ecriture() { Assert.Empty(await _envies.ListerAsync(null)); Assert.NotNull((await AjouterAsync("Médor", utilisateur: null!)).Erreur); Assert.True((await _envies.ModifierAsync( 1, new EnregistrementRevueSouhaitee { Titre = "Médor" }, null)).NExistePas); Assert.False(await _envies.SupprimerAsync(1, null)); } [Fact] public async Task Le_titre_est_obligatoire() { Assert.NotNull((await AjouterAsync(" ")).Erreur); Assert.Empty(_db.RevuesSouhaitees); } public void Dispose() { _db.Dispose(); _connexion.Dispose(); GC.SuppressFinalize(this); } }