Format disait physique ou numérique, jamais roman ou BD. Trois valeurs seulement : une valeur s'ajoute sans migration, la renommer une fois posée sur des centaines de fiches, non. Le défaut est « non précisé », pas « roman » : les fiches existantes n'ont pas été saisies avec cette question en tête, et un AddColumn suffit justement parce que la valeur par défaut ne prétend rien. L'interface n'affiche donc que ce qui a été choisi, comme pour le format. Filtre ajouté aux DEUX implémentations, serveur et hors-ligne, avec quatre jeux de critères de plus dans le test qui les confronte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
494 lines
19 KiB
C#
494 lines
19 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// CRUD du catalogue, sur une base SQLite <b>en mémoire</b> : même moteur qu'en production,
|
|
/// aucun fichier, aucun réseau.
|
|
/// </summary>
|
|
public class ServiceCatalogueTests : IDisposable
|
|
{
|
|
private readonly SqliteConnection _connexion;
|
|
private readonly MaBibliDbContext _db;
|
|
private readonly ServiceCatalogue _service;
|
|
|
|
public ServiceCatalogueTests()
|
|
{
|
|
// La connexion doit rester ouverte : SQLite détruit une base « :memory: » à sa fermeture.
|
|
_connexion = new SqliteConnection("Data Source=:memory:");
|
|
_connexion.Open();
|
|
|
|
var options = new DbContextOptionsBuilder<MaBibliDbContext>()
|
|
.UseSqlite(_connexion)
|
|
.Options;
|
|
|
|
_db = new MaBibliDbContext(options);
|
|
_db.Database.EnsureCreated();
|
|
|
|
_service = new ServiceCatalogue(_db, new ServiceAuteurs(_db));
|
|
}
|
|
|
|
private static EnregistrementLivre Saisie(
|
|
string titre,
|
|
string? auteur = null,
|
|
string? isbn = null,
|
|
Format format = Format.Physique,
|
|
Statut? statut = null) => new()
|
|
{
|
|
Titre = titre,
|
|
Auteur = auteur,
|
|
Isbn = isbn,
|
|
Format = format,
|
|
Statut = statut,
|
|
};
|
|
|
|
[Fact]
|
|
public async Task Creer_enregistre_le_livre_et_trace_lauteur_de_la_saisie()
|
|
{
|
|
var resultat = await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu");
|
|
|
|
Assert.Null(resultat.Erreur);
|
|
var livre = resultat.Livre!;
|
|
Assert.True(livre.Id > 0);
|
|
Assert.Equal("Germinal", livre.Titre);
|
|
Assert.Equal("mathieu", livre.AjoutePar);
|
|
Assert.Equal(["Émile Zola"], livre.Auteurs.Select(a => a.Nom));
|
|
Assert.NotEqual(default, livre.DateAjout);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Creer_normalise_lisbn()
|
|
{
|
|
var resultat = await _service.CreerAsync(Saisie("Germinal", isbn: "978-2-253-00422-6"), "mathieu");
|
|
|
|
Assert.Equal("9782253004226", resultat.Livre!.Isbn);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Creer_refuse_un_titre_vide()
|
|
{
|
|
var resultat = await _service.CreerAsync(Saisie(" "), "mathieu");
|
|
|
|
Assert.Null(resultat.Livre);
|
|
Assert.NotNull(resultat.Erreur);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Creer_refuse_un_isbn_invalide_mais_accepte_labsence_disbn()
|
|
{
|
|
var invalide = await _service.CreerAsync(Saisie("Sans titre", isbn: "1234567890123"), "mathieu");
|
|
Assert.Null(invalide.Livre);
|
|
Assert.NotNull(invalide.Erreur);
|
|
|
|
// Certains livres n'ont pas d'ISBN : l'absence doit rester parfaitement légitime.
|
|
var sansIsbn = await _service.CreerAsync(Saisie("Carnet de famille"), "mathieu");
|
|
Assert.Null(sansIsbn.Erreur);
|
|
Assert.Null(sansIsbn.Livre!.Isbn);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Le_catalogue_est_unique_physiques_et_numeriques_cohabitent()
|
|
{
|
|
await _service.CreerAsync(Saisie("Papier", format: Format.Physique), "mathieu");
|
|
await _service.CreerAsync(Saisie("Epub", format: Format.Numerique), "mathieu");
|
|
|
|
var tout = await _service.ListerAsync(new CritereLivres(), "mathieu");
|
|
Assert.Equal(2, tout.Count);
|
|
|
|
var numeriques = await _service.ListerAsync(new CritereLivres { Format = Format.Numerique }, "mathieu");
|
|
Assert.Equal(["Epub"], numeriques.Select(l => l.Titre));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task La_recherche_traverse_bien_SQLite()
|
|
{
|
|
await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu");
|
|
await _service.CreerAsync(Saisie("Le Horla", "Guy de Maupassant"), "mathieu");
|
|
|
|
var parAuteur = await _service.ListerAsync(new CritereLivres { Recherche = "maupassant" }, "mathieu");
|
|
Assert.Equal(["Le Horla"], parAuteur.Select(l => l.Titre));
|
|
|
|
var parTitre = await _service.ListerAsync(new CritereLivres { Recherche = "GERMI" }, "mathieu");
|
|
Assert.Equal(["Germinal"], parTitre.Select(l => l.Titre));
|
|
}
|
|
|
|
/// <summary>
|
|
/// La recherche sans accents doit traverser SQLite, pas seulement LINQ-to-Objects : c'est là
|
|
/// que la colonne normalisée gagne son existence.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineData("emile")]
|
|
[InlineData("EMILE")]
|
|
[InlineData("Émile Zola")]
|
|
public async Task La_recherche_sans_accents_traverse_bien_SQLite(string terme)
|
|
{
|
|
await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu");
|
|
await _service.CreerAsync(Saisie("Le Horla", "Guy de Maupassant"), "mathieu");
|
|
|
|
var resultat = await _service.ListerAsync(new CritereLivres { Recherche = terme }, "mathieu");
|
|
|
|
Assert.Equal(["Germinal"], resultat.Select(l => l.Titre));
|
|
}
|
|
|
|
/// <summary>
|
|
/// <b>Invariant central</b> : la bibliothèque est commune. Des livres saisis par des comptes
|
|
/// différents doivent tous ressortir, quelle que soit la personne qui consulte.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task La_liste_renvoie_les_livres_de_tous_les_utilisateurs()
|
|
{
|
|
await _service.CreerAsync(Saisie("Livre de Mathieu"), "mathieu");
|
|
await _service.CreerAsync(Saisie("Livre de Camille"), "camille");
|
|
await _service.CreerAsync(Saisie("Livre anonyme"), null);
|
|
|
|
var tout = await _service.ListerAsync(new CritereLivres(), "mathieu");
|
|
|
|
Assert.Equal(3, tout.Count);
|
|
Assert.Contains(tout, l => l.AjoutePar == "mathieu");
|
|
Assert.Contains(tout, l => l.AjoutePar == "camille");
|
|
Assert.Contains(tout, l => l.AjoutePar is null);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Modifier_ne_reecrit_ni_AjoutePar_ni_DateAjout()
|
|
{
|
|
var cree = (await _service.CreerAsync(Saisie("Titre initial", "Auteur Initial"), "camille")).Livre!;
|
|
|
|
var modifie = await _service.ModifierAsync(cree.Id, new EnregistrementLivre
|
|
{
|
|
Titre = "Titre corrigé",
|
|
Auteur = "Auteur Corrigé",
|
|
Format = Format.Numerique,
|
|
Statut = Statut.Lu,
|
|
}, "camille");
|
|
|
|
Assert.Null(modifie.Erreur);
|
|
Assert.Equal("Titre corrigé", modifie.Livre!.Titre);
|
|
Assert.Equal(Format.Numerique, modifie.Livre.Format);
|
|
Assert.Equal(["Auteur Corrigé"], modifie.Livre.Auteurs.Select(a => a.Nom));
|
|
|
|
// La trace de la saisie d'origine survit à l'édition, y compris par quelqu'un d'autre.
|
|
Assert.Equal("camille", modifie.Livre.AjoutePar);
|
|
Assert.Equal(cree.DateAjout, modifie.Livre.DateAjout);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Modifier_un_livre_inexistant_est_signale_comme_introuvable()
|
|
{
|
|
var resultat = await _service.ModifierAsync(4242, Saisie("Fantôme"), "mathieu");
|
|
|
|
Assert.True(resultat.EstIntrouvable);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Changer_le_statut_ne_touche_que_le_statut()
|
|
{
|
|
var cree = (await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu")).Livre!;
|
|
|
|
var apres = await _service.ChangerStatutAsync(cree.Id, Statut.EnCours, "mathieu");
|
|
|
|
Assert.NotNull(apres);
|
|
Assert.Equal(Statut.EnCours, apres!.Statut);
|
|
Assert.Equal(cree.Titre, apres.Titre);
|
|
Assert.Equal(cree.Auteurs.Select(a => a.Nom), apres.Auteurs.Select(a => a.Nom));
|
|
Assert.Equal(cree.AjoutePar, apres.AjoutePar);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <b>La décision de cette phase, vérifiée de bout en bout.</b> Un seul livre, deux personnes,
|
|
/// deux statuts qui ne se marchent pas dessus.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task Deux_utilisateurs_ont_des_statuts_differents_sur_le_meme_livre()
|
|
{
|
|
var livre = (await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu")).Livre!;
|
|
|
|
await _service.ChangerStatutAsync(livre.Id, Statut.Lu, "mathieu");
|
|
await _service.ChangerStatutAsync(livre.Id, Statut.EnCours, "camille");
|
|
|
|
Assert.Equal(Statut.Lu, (await _service.ObtenirAsync(livre.Id, "mathieu"))!.Statut);
|
|
Assert.Equal(Statut.EnCours, (await _service.ObtenirAsync(livre.Id, "camille"))!.Statut);
|
|
|
|
// Une troisième personne n'a rien posé : pour elle le livre est « non commencé ».
|
|
Assert.Null((await _service.ObtenirAsync(livre.Id, "claire"))!.Statut);
|
|
|
|
// Une seule ligne par personne : rien ne s'empile à force de changer d'avis.
|
|
Assert.Equal(2, await _db.StatutsLecture.CountAsync(s => s.LivreId == livre.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_livre_cree_sans_statut_nen_ecrit_aucun()
|
|
{
|
|
var livre = (await _service.CreerAsync(Saisie("Germinal"), "mathieu")).Livre!;
|
|
|
|
Assert.Null((await _service.ObtenirAsync(livre.Id, "mathieu"))!.Statut);
|
|
Assert.Empty(_db.StatutsLecture);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_statut_nul_remet_le_livre_a_non_commence()
|
|
{
|
|
var livre = (await _service.CreerAsync(Saisie("Germinal", statut: Statut.Lu), "mathieu")).Livre!;
|
|
Assert.Equal(Statut.Lu, livre.Statut);
|
|
|
|
var apres = await _service.ChangerStatutAsync(livre.Id, null, "mathieu");
|
|
|
|
Assert.Null(apres!.Statut);
|
|
Assert.Empty(_db.StatutsLecture);
|
|
}
|
|
|
|
/// <summary>Éditer un livre pose MON statut, jamais celui de la personne qui l'a saisi.</summary>
|
|
[Fact]
|
|
public async Task Modifier_un_livre_ne_touche_pas_au_statut_des_autres()
|
|
{
|
|
var livre = (await _service.CreerAsync(Saisie("Germinal", statut: Statut.Lu), "mathieu")).Livre!;
|
|
|
|
await _service.ModifierAsync(
|
|
livre.Id, Saisie("Germinal", statut: Statut.EnCours), "camille");
|
|
|
|
Assert.Equal(Statut.Lu, (await _service.ObtenirAsync(livre.Id, "mathieu"))!.Statut);
|
|
Assert.Equal(Statut.EnCours, (await _service.ObtenirAsync(livre.Id, "camille"))!.Statut);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Changer_le_statut_dun_livre_inexistant_renvoie_null()
|
|
=> Assert.Null(await _service.ChangerStatutAsync(4242, Statut.Lu, "mathieu"));
|
|
|
|
[Fact]
|
|
public async Task Sans_identite_aucun_statut_nest_ecrit()
|
|
{
|
|
var livre = (await _service.CreerAsync(Saisie("Anonyme", statut: Statut.Lu), null)).Livre!;
|
|
|
|
Assert.Null(livre.Statut);
|
|
Assert.Empty(_db.StatutsLecture);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_livre_peut_avoir_plusieurs_auteurs_dans_lordre()
|
|
{
|
|
var livre = (await _service.CreerAsync(
|
|
Saisie("Introduction to Algorithms",
|
|
"Thomas H. Cormen; Charles E. Leiserson; Ronald L. Rivest; Clifford Stein"),
|
|
"mathieu")).Livre!;
|
|
|
|
Assert.Equal(
|
|
["Thomas H. Cormen", "Charles E. Leiserson", "Ronald L. Rivest", "Clifford Stein"],
|
|
livre.Auteurs.Select(a => a.Nom));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Les_variantes_sures_dun_auteur_partagent_la_meme_fiche()
|
|
{
|
|
var a = (await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu")).Livre!;
|
|
var b = (await _service.CreerAsync(Saisie("Nana", "Zola, Emile"), "mathieu")).Livre!;
|
|
var c = (await _service.CreerAsync(Saisie("La Curée", "E. Zola"), "mathieu")).Livre!;
|
|
|
|
var id = a.Auteurs.Single().Id;
|
|
Assert.Equal(id, b.Auteurs.Single().Id);
|
|
Assert.Equal(id, c.Auteurs.Single().Id);
|
|
Assert.Equal(1, await _db.Auteurs.CountAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_patronyme_seul_ne_se_fond_pas_dans_un_nom_complet()
|
|
{
|
|
var complet = (await _service.CreerAsync(
|
|
Saisie("Pandora's Star", "Peter F. Hamilton"), "mathieu")).Livre!;
|
|
var seul = (await _service.CreerAsync(Saisie("Autre livre", "Hamilton"), "mathieu")).Livre!;
|
|
|
|
// Deux fiches distinctes : le rapprochement est ambigu, il sera proposé, pas appliqué.
|
|
Assert.NotEqual(complet.Auteurs.Single().Id, seul.Auteurs.Single().Id);
|
|
Assert.Equal(2, await _db.Auteurs.CountAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Supprimer_retire_le_livre_du_catalogue()
|
|
{
|
|
var cree = (await _service.CreerAsync(Saisie("À jeter", "Auteur Jetable"), "mathieu")).Livre!;
|
|
|
|
Assert.True(await _service.SupprimerAsync(cree.Id));
|
|
Assert.Null(await _service.ObtenirAsync(cree.Id, "mathieu"));
|
|
Assert.Empty(await _service.ListerAsync(new CritereLivres(), "mathieu"));
|
|
|
|
// L'auteur n'a plus aucun livre : il ne doit pas rester à polluer l'écran des auteurs.
|
|
Assert.Empty(_db.Auteurs);
|
|
|
|
// Seconde suppression : plus rien à supprimer.
|
|
Assert.False(await _service.SupprimerAsync(cree.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_auteur_encore_lu_par_un_autre_livre_survit_a_une_suppression()
|
|
{
|
|
var premier = (await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu")).Livre!;
|
|
await _service.CreerAsync(Saisie("Nana", "Émile Zola"), "mathieu");
|
|
|
|
await _service.SupprimerAsync(premier.Id);
|
|
|
|
Assert.Equal(1, await _db.Auteurs.CountAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Obtenir_renvoie_la_fiche_complete()
|
|
{
|
|
var cree = (await _service.CreerAsync(
|
|
Saisie("Le Horla", "Guy de Maupassant", "9782080704092"), "mathieu")).Livre!;
|
|
|
|
var relu = await _service.ObtenirAsync(cree.Id, "mathieu");
|
|
|
|
Assert.NotNull(relu);
|
|
Assert.Equal("Le Horla", relu!.Titre);
|
|
Assert.Equal("9782080704092", relu.Isbn);
|
|
Assert.Equal(["Guy de Maupassant"], relu.Auteurs.Select(a => a.Nom));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_livre_sans_type_precise_reste_non_precise()
|
|
{
|
|
var livre = (await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu")).Livre!;
|
|
|
|
// ⚠️ Le défaut n'est PAS « Roman » : on ne remplit pas une case à la place de
|
|
// l'utilisateur, et l'interface n'affiche que ce qui a été choisi.
|
|
Assert.Equal(TypeDocument.NonPrecise, livre.TypeDocument);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Le_type_de_document_est_enregistre_puis_modifiable()
|
|
{
|
|
var saisie = Saisie("Les fourmis", "Bernard Werber");
|
|
saisie.TypeDocument = TypeDocument.BandeDessinee;
|
|
|
|
var cree = (await _service.CreerAsync(saisie, "mathieu")).Livre!;
|
|
Assert.Equal(TypeDocument.BandeDessinee, cree.TypeDocument);
|
|
|
|
saisie.TypeDocument = TypeDocument.Roman;
|
|
var modifie = (await _service.ModifierAsync(cree.Id, saisie, "mathieu")).Livre!;
|
|
|
|
Assert.Equal(TypeDocument.Roman, modifie.TypeDocument);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Le_catalogue_se_filtre_par_type_de_document()
|
|
{
|
|
var bd = Saisie("Les fourmis", "Bernard Werber");
|
|
bd.TypeDocument = TypeDocument.BandeDessinee;
|
|
await _service.CreerAsync(bd, "mathieu");
|
|
await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu");
|
|
|
|
var bds = await _service.ListerAsync(
|
|
new CritereLivres { TypeDocument = TypeDocument.BandeDessinee }, "mathieu");
|
|
var nonPrecises = await _service.ListerAsync(
|
|
new CritereLivres { TypeDocument = TypeDocument.NonPrecise }, "mathieu");
|
|
|
|
Assert.Equal(["Les fourmis"], bds.Select(l => l.Titre));
|
|
Assert.Equal(["Germinal"], nonPrecises.Select(l => l.Titre));
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
// Doublons : un AVERTISSEMENT, jamais un refus. Posséder deux exemplaires est légitime —
|
|
// on garde le sien et on prête l'autre — et l'ISBN est facultatif, donc il ne peut pas
|
|
// servir de clé unique. Ces tests verrouillent les deux moitiés : ce qui est signalé, et
|
|
// le fait qu'une confirmation suffise toujours à passer outre.
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public async Task Le_meme_isbn_est_signale_sans_rien_ecrire()
|
|
{
|
|
await _service.CreerAsync(Saisie("Le Horla", "Guy de Maupassant", "9782080704092"), "mathieu");
|
|
|
|
var second = await _service.CreerAsync(
|
|
Saisie("Le Horla", "Guy de Maupassant", "9782080704092"), "mathieu");
|
|
|
|
Assert.Null(second.Livre);
|
|
Assert.Null(second.Erreur);
|
|
var doublons = second.Doublons!;
|
|
Assert.Contains("Le Horla", doublons.Message);
|
|
Assert.Single(doublons.Livres);
|
|
Assert.Equal(1, await _db.Livres.CountAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Le_meme_isbn_confirme_cree_bien_un_second_exemplaire()
|
|
{
|
|
await _service.CreerAsync(Saisie("Le Horla", "Guy de Maupassant", "9782080704092"), "mathieu");
|
|
|
|
var second = await _service.CreerAsync(
|
|
Saisie("Le Horla", "Guy de Maupassant", "9782080704092"), "mathieu", confirmerDoublon: true);
|
|
|
|
Assert.Null(second.Doublons);
|
|
Assert.NotNull(second.Livre);
|
|
Assert.Equal(2, await _db.Livres.CountAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Sans_isbn_le_titre_et_lauteur_suffisent_a_signaler()
|
|
{
|
|
await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu");
|
|
|
|
// Sous-titre, accents et ordre du nom diffèrent : c'est exactement ce que CleOeuvre et
|
|
// RapprochementAuteurs sont là pour absorber.
|
|
var second = await _service.CreerAsync(Saisie("Germinal : roman", "Zola, Emile"), "camille");
|
|
|
|
Assert.NotNull(second.Doublons);
|
|
Assert.Single(second.Doublons!.Livres);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_titre_identique_dun_autre_auteur_nest_pas_un_doublon()
|
|
{
|
|
await _service.CreerAsync(Saisie("Nouvelles", "Guy de Maupassant"), "mathieu");
|
|
|
|
var autre = await _service.CreerAsync(Saisie("Nouvelles", "Anton Tchekhov"), "mathieu");
|
|
|
|
Assert.Null(autre.Doublons);
|
|
Assert.NotNull(autre.Livre);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_titre_seulement_commence_pareil_nest_pas_un_doublon()
|
|
{
|
|
await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu");
|
|
|
|
// Le préfixe attrape le candidat en SQL, la clé d'œuvre le rejette ensuite en mémoire :
|
|
// sans cette seconde passe, tout ce qui commence par « Germinal » serait signalé.
|
|
var autre = await _service.CreerAsync(Saisie("Germinal les années noires", "Émile Zola"), "mathieu");
|
|
|
|
Assert.Null(autre.Doublons);
|
|
Assert.NotNull(autre.Livre);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_livre_trouve_par_son_isbn_ET_son_titre_nest_signale_quune_fois()
|
|
{
|
|
await _service.CreerAsync(Saisie("Germinal", "Émile Zola", "9782253004226"), "mathieu");
|
|
|
|
var second = await _service.CreerAsync(Saisie("Germinal", "Émile Zola", "9782253004226"), "mathieu");
|
|
|
|
Assert.Single(second.Doublons!.Livres);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Une_saisie_invalide_est_une_erreur_avant_detre_un_doublon()
|
|
{
|
|
await _service.CreerAsync(Saisie("Germinal", "Émile Zola"), "mathieu");
|
|
|
|
var invalide = await _service.CreerAsync(Saisie("Germinal", "Émile Zola", "1234567890123"), "mathieu");
|
|
|
|
Assert.NotNull(invalide.Erreur);
|
|
Assert.Null(invalide.Doublons);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_db.Dispose();
|
|
_connexion.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|