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>
236 lines
7.9 KiB
C#
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);
|
|
}
|
|
}
|