Files
mabibli/MaBibli.Tests/ServiceSeriesTests.cs
T

290 lines
11 KiB
C#

using MaBibli.Api.Data;
using MaBibli.Api.Services.Catalogue;
using MaBibli.Api.Services.Prets;
using MaBibli.Api.Services.Series;
using MaBibli.Api.Services.Souhaits;
using MaBibli.Shared.Dtos;
using MaBibli.Shared.Entites;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace MaBibli.Tests;
/// <summary>
/// Sagas, cycles et séries, sur une base SQLite <b>en mémoire</b> : les invariants tenus ici
/// sont pour moitié des index (unicité du titre, un livre une seule fois par série) et ne se
/// vérifieraient pas sur un faux dépôt.
/// </summary>
/// <remarks>
/// Ce qui est protégé en priorité : <b>un tome sans livre est normal</b> (c'est le trou qu'on
/// vient voir), une suppression de livre <b>laisse</b> le trou, et l'ordre de lecture est
/// stocké — jamais déduit.
/// </remarks>
public class ServiceSeriesTests : IDisposable
{
private readonly SqliteConnection _connexion;
private readonly MaBibliDbContext _db;
private readonly ServiceSeries _series;
private readonly ServiceCatalogue _catalogue;
private readonly ServiceSouhaits _souhaits;
private const string Mathieu = "mathieu";
private const string Camille = "camille";
public ServiceSeriesTests()
{
_connexion = new SqliteConnection("Data Source=:memory:");
_connexion.Open();
var options = new DbContextOptionsBuilder<MaBibliDbContext>()
.UseSqlite(_connexion)
.Options;
_db = new MaBibliDbContext(options);
_db.Database.EnsureCreated();
_souhaits = new ServiceSouhaits(_db);
_series = new ServiceSeries(_db, _souhaits);
_catalogue = new ServiceCatalogue(_db, new ServiceAuteurs(_db));
}
private async Task<int> LivreAsync(string titre, string auteur = "R. A. Salvatore")
{
var resultat = await _catalogue.CreerAsync(
new EnregistrementLivre { Titre = titre, Auteur = auteur }, Mathieu, confirmerDoublon: true);
return resultat.Livre!.Id;
}
private async Task<SerieDto> SerieAsync(string titre, int? parenteId = null)
{
var resultat = await _series.CreerAsync(
new EnregistrementSerie { Titre = titre, SerieParenteId = parenteId }, Mathieu);
Assert.Null(resultat.Erreur);
return resultat.Serie!;
}
[Fact]
public async Task Une_serie_melange_tomes_possedes_et_tomes_manquants()
{
var serie = await SerieAsync("L'Elfe noir");
var livreId = await LivreAsync("Terre natale");
await _series.AjouterElementAsync(serie.Id, new AjoutElementSerie { LivreId = livreId });
var apres = (await _series.AjouterElementAsync(
serie.Id, new AjoutElementSerie { Titre = "Terre d'exil" })).Serie!;
Assert.Equal(["Terre natale", "Terre d'exil"], apres.Elements.Select(e => e.Titre));
Assert.Equal([true, false], apres.Elements.Select(e => e.Possede));
// Le compteur de l'écran : « 1 sur 2 ».
Assert.Equal(1, apres.NombrePossedes);
}
[Fact]
public async Task Un_tome_sans_titre_ni_livre_est_refuse()
{
var serie = await SerieAsync("L'Elfe noir");
var resultat = await _series.AjouterElementAsync(serie.Id, new AjoutElementSerie());
Assert.NotNull(resultat.Erreur);
}
[Fact]
public async Task Supprimer_un_livre_laisse_un_trou_nomme_dans_la_serie()
{
var serie = await SerieAsync("L'Elfe noir");
var livreId = await LivreAsync("Terre natale");
await _series.AjouterElementAsync(serie.Id, new AjoutElementSerie { LivreId = livreId });
await _catalogue.SupprimerAsync(livreId);
// ⚠️ L'invariant décisif : la place survit, avec son titre. Une cascade effacerait le
// tome 1 de la saga parce qu'on a perdu son exemplaire.
var element = (await _series.ListerAsync()).Single().Elements.Single();
Assert.Equal("Terre natale", element.Titre);
Assert.False(element.Possede);
}
[Fact]
public async Task Lordre_de_lecture_est_stocke_et_se_reordonne()
{
var serie = await SerieAsync("L'Elfe noir");
var avant = serie;
foreach (var titre in new[] { "Terre natale", "Terre d'exil", "Terre promise" })
{
avant = (await _series.AjouterElementAsync(
serie.Id, new AjoutElementSerie { Titre = titre })).Serie!;
}
var inverse = avant.Elements.Select(e => e.Id).Reverse().ToList();
var apres = (await _series.ReordonnerAsync(serie.Id, inverse)).Serie!;
Assert.Equal(["Terre promise", "Terre d'exil", "Terre natale"], apres.Elements.Select(e => e.Titre));
Assert.Equal([0, 1, 2], apres.Elements.Select(e => e.Position));
}
[Fact]
public async Task Un_tome_absent_de_lordre_recu_est_conserve_a_la_suite()
{
var serie = await SerieAsync("L'Elfe noir");
var un = (await _series.AjouterElementAsync(serie.Id, new AjoutElementSerie { Titre = "Un" })).Serie!;
await _series.AjouterElementAsync(serie.Id, new AjoutElementSerie { Titre = "Deux" });
// Le client ne connaissait que le premier tome : le second ne doit pas disparaître.
var apres = (await _series.ReordonnerAsync(serie.Id, [un.Elements[0].Id])).Serie!;
Assert.Equal(["Un", "Deux"], apres.Elements.Select(e => e.Titre));
}
[Fact]
public async Task Un_meme_livre_ne_tient_quune_place_dans_une_serie_mais_vit_dans_plusieurs()
{
var serie = await SerieAsync("L'Elfe noir");
var autre = await SerieAsync("Les Royaumes oubliés");
var livreId = await LivreAsync("Terre natale");
await _series.AjouterElementAsync(serie.Id, new AjoutElementSerie { LivreId = livreId });
var doublon = await _series.AjouterElementAsync(serie.Id, new AjoutElementSerie { LivreId = livreId });
var ailleurs = await _series.AjouterElementAsync(autre.Id, new AjoutElementSerie { LivreId = livreId });
Assert.NotNull(doublon.Erreur);
// Un livre appartient légitimement à plusieurs regroupements : une série, un cycle,
// une intégrale.
Assert.Null(ailleurs.Erreur);
}
[Fact]
public async Task Une_serie_se_range_dans_un_cycle_mais_jamais_dans_elle_meme()
{
var cycle = await SerieAsync("La Légende de Drizzt");
var trilogie = await SerieAsync("L'Elfe noir");
var range = await _series.ModifierAsync(
trilogie.Id, new EnregistrementSerie { Titre = trilogie.Titre, SerieParenteId = cycle.Id });
Assert.Equal(cycle.Id, range.Serie!.SerieParenteId);
// Refermer l'arbre sur lui-même ferait boucler l'affichage à l'infini.
var boucle = await _series.ModifierAsync(
cycle.Id, new EnregistrementSerie { Titre = cycle.Titre, SerieParenteId = trilogie.Id });
Assert.NotNull(boucle.Erreur);
}
[Fact]
public async Task Lordre_des_sous_series_se_reordonne_sans_toucher_aux_tomes()
{
var cycle = await SerieAsync("Cycle");
var une = await SerieAsync("Une", cycle.Id);
var deux = await SerieAsync("Deux", cycle.Id);
var apres = (await _series.ReordonnerSousSeriesAsync(cycle.Id, [deux.Id, une.Id])).Serie!;
Assert.Equal([deux.Id, une.Id],
(await _series.ListerAsync()).Where(s => s.SerieParenteId == cycle.Id)
.OrderBy(s => s.Position).Select(s => s.Id));
Assert.Empty(apres.Elements);
}
[Fact]
public async Task Supprimer_un_cycle_ne_detruit_pas_les_series_quil_contient()
{
var cycle = await SerieAsync("La Légende de Drizzt");
var trilogie = await SerieAsync("L'Elfe noir", cycle.Id);
await _series.SupprimerAsync(cycle.Id);
var restantes = await _series.ListerAsync();
var seule = Assert.Single(restantes);
Assert.Equal(trilogie.Id, seule.Id);
Assert.Null(seule.SerieParenteId);
}
[Fact]
public async Task Deux_series_ne_peuvent_pas_porter_le_meme_titre()
{
await SerieAsync("L'Elfe noir");
var doublon = await _series.CreerAsync(
new EnregistrementSerie { Titre = "l'elfe noir" }, Mathieu);
Assert.NotNull(doublon.Erreur);
}
[Fact]
public async Task Un_tome_manquant_part_dans_la_liste_denvies_de_lappelant()
{
var serie = await SerieAsync("L'Elfe noir");
var apres = (await _series.AjouterElementAsync(
serie.Id, new AjoutElementSerie { Titre = "Terre d'exil" })).Serie!;
var resultat = await _series.MettreEnEnviesAsync(apres.Elements[0].Id, Camille);
Assert.Null(resultat.Erreur);
// ⚠️ La série est COMMUNE, l'envie est PERSONNELLE : elle n'apparaît que chez Camille.
Assert.Equal(["Terre d'exil"], (await _souhaits.ListerAsync(Camille)).Select(s => s.Titre));
Assert.Empty(await _souhaits.ListerAsync(Mathieu));
}
[Fact]
public async Task Un_tome_deja_possede_ne_part_pas_en_envies()
{
var serie = await SerieAsync("L'Elfe noir");
var livreId = await LivreAsync("Terre natale");
var apres = (await _series.AjouterElementAsync(
serie.Id, new AjoutElementSerie { LivreId = livreId })).Serie!;
var resultat = await _series.MettreEnEnviesAsync(apres.Elements[0].Id, Mathieu);
Assert.NotNull(resultat.Erreur);
Assert.Empty(await _souhaits.ListerAsync(Mathieu));
}
[Fact]
public async Task Detacher_un_livre_laisse_la_place_avec_son_titre()
{
var serie = await SerieAsync("L'Elfe noir");
var livreId = await LivreAsync("Terre natale");
var apres = (await _series.AjouterElementAsync(
serie.Id, new AjoutElementSerie { LivreId = livreId, Titre = "Tome 1" })).Serie!;
var detache = (await _series.RattacherLivreAsync(apres.Elements[0].Id, null)).Serie!;
Assert.False(detache.Elements[0].Possede);
Assert.Equal("Tome 1", detache.Elements[0].Titre);
// Le livre, lui, n'a pas bougé du catalogue.
Assert.NotNull(await _catalogue.ObtenirAsync(livreId, Mathieu));
}
[Fact]
public async Task Un_tome_prete_le_dit_dans_la_serie()
{
var serie = await SerieAsync("L'Elfe noir");
var livreId = await LivreAsync("Terre natale");
await _series.AjouterElementAsync(serie.Id, new AjoutElementSerie { LivreId = livreId });
var prets = new ServicePrets(_db);
await prets.PreterAsync(livreId, new EnregistrementPret { Emprunteur = "Paul" });
var element = (await _series.ListerAsync()).Single().Elements.Single();
// « Où est le tome 1 ? » a une seule réponse, la même pour tout le foyer.
Assert.Equal("Paul", element.PreteA);
}
public void Dispose()
{
_db.Dispose();
_connexion.Dispose();
GC.SuppressFinalize(this);
}
}