Files
mabibli/MaBibli.Tests/ServiceRevuesSouhaiteesTests.cs
T
Mathieu LimonierandClaude Opus 5 6a6d745af4 MaBibli 1.0.0
Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts,
scan de code-barres, consultation hors-ligne.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 22:36:16 +02:00

236 lines
7.9 KiB
C#

using MaBibli.Api.Data;
using MaBibli.Api.Services.Souhaits;
using MaBibli.Shared.Dtos;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace MaBibli.Tests;
/// <summary>
/// Les envies de revues, sur une base SQLite <b>en mémoire</b> : l'unicité
/// (utilisateur, titre, numéro) n'existe qu'en base.
/// </summary>
/// <remarks>
/// Mêmes invariants que la liste d'envies de livres, et ils comptent autant : la table est
/// <b>personnelle</b>, et l'envie d'un autre doit être introuvable, jamais refusée.
/// </remarks>
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<MaBibliDbContext>().UseSqlite(_connexion).Options);
_db.Database.EnsureCreated();
_envies = new ServiceRevuesSouhaitees(_db);
}
private Task<ResultatRevueSouhaitee> 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);
/// <summary>
/// L'ISSN est le seul code du projet rangé <b>avec son tiret</b> : 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.
/// </summary>
[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());
}
/// <summary>
/// ⚠️ Le piège que la colonne <c>NumeroNormalise</c> non nullable ferme : SQLite tient deux
/// <c>NULL</c> pour distincts, et « Médor, sans numéro » s'ajouterait autant de fois qu'on
/// cliquerait.
/// </summary>
[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());
}
/// <summary>
/// ⚠️ Sans exclusion explicite de sa propre ligne, toute édition se dirait doublon
/// d'elle-même — et il serait impossible de corriger une note.
/// </summary>
[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);
}
}