Signaler les doublons à l'ajout d'un livre, sans jamais les refuser
Rien n'empêchait de rescanner un livre déjà catalogué. Plutôt qu'un index unique — qui interdirait le second exemplaire, cas parfaitement légitime — la création répond 409 avec les fiches semblables, et « confirmerDoublon » enregistre la même saisie. Deux critères, dont aucun n'est une clé : ISBN identique, ou clé d'œuvre et auteur communs. La clé d'œuvre étant un préfixe de TitreNormalise, SQL dégrossit sur la colonne indexée et l'égalité exacte se vérifie ensuite en mémoire. Vérifié en exécution : avertissement, retour au formulaire intact, et ajout confirmé créant bien un second exemplaire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -348,6 +348,100 @@ public class ServiceCatalogueTests : IDisposable
|
||||
Assert.Equal(["Guy de Maupassant"], relu.Auteurs.Select(a => a.Nom));
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// 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();
|
||||
|
||||
Reference in New Issue
Block a user