Clôt la décision du premier lot, qui écartait les périodiques faute de modèle. Douze numéros d'un magazine ne font plus douze fiches identiques. Table à part, pour la troisième fois et pour le même motif que la liste d'envies et les séries : logée dans Livres, une revue entrerait dans le catalogue, ses compteurs, les doublons et les bibliographies, et il faudrait écrire « et qui n'est pas une revue » à chaque lecture. Un numéro est recensé, rien de plus — ni prêt ni statut, tous deux liés à Livre par clé étrangère. Réversible. « Créer ou retrouver » et non « créer » : scanner le numéro suivant du même magazine retombe forcément sur la même revue. L'unicité de l'ISSN est un index partiel, sans quoi les revues sans ISSN se bloqueraient entre elles. Le flux 977 mène désormais à la fiche de la revue, et ne pré-remplit plus le formulaire d'un livre. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
181 lines
5.7 KiB
C#
181 lines
5.7 KiB
C#
using MaBibli.Api.Data;
|
|
using MaBibli.Api.Services.Revues;
|
|
using MaBibli.Shared.Dtos;
|
|
using Microsoft.Data.Sqlite;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace MaBibli.Tests;
|
|
|
|
/// <summary>
|
|
/// Revues et numéros, sur une base SQLite <b>en mémoire</b> : l'unicité d'un ISSN passe par un
|
|
/// index partiel, qui n'existe qu'en base.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// L'invariant central : <b>une fiche par revue</b>, les numéros à l'intérieur. C'est ce qui
|
|
/// évite douze fiches identiques pour douze numéros d'un même magazine.
|
|
/// </remarks>
|
|
public class ServiceRevuesTests : IDisposable
|
|
{
|
|
private readonly SqliteConnection _connexion;
|
|
private readonly MaBibliDbContext _db;
|
|
private readonly ServiceRevues _revues;
|
|
|
|
private const string Mathieu = "mathieu";
|
|
|
|
public ServiceRevuesTests()
|
|
{
|
|
_connexion = new SqliteConnection("Data Source=:memory:");
|
|
_connexion.Open();
|
|
|
|
var options = new DbContextOptionsBuilder<MaBibliDbContext>()
|
|
.UseSqlite(_connexion)
|
|
.Options;
|
|
|
|
_db = new MaBibliDbContext(options);
|
|
_db.Database.EnsureCreated();
|
|
|
|
_revues = new ServiceRevues(_db);
|
|
}
|
|
|
|
private async Task<RevueDto> RevueAsync(string titre = "Médor", string? issn = "2466-6718")
|
|
{
|
|
var resultat = await _revues.CreerOuRetrouverAsync(
|
|
new EnregistrementRevue { Titre = titre, Issn = issn }, Mathieu);
|
|
|
|
Assert.Null(resultat.Erreur);
|
|
return resultat.Revue!;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Rescanner_la_meme_revue_ne_cree_pas_une_seconde_fiche()
|
|
{
|
|
var premiere = await RevueAsync();
|
|
|
|
// Le second numéro du même magazine porte le même ISSN : c'est le cas NORMAL du flux
|
|
// « 977 », et il ne doit surtout pas produire une fiche de plus.
|
|
var seconde = await RevueAsync("Médor (Bruxelles)");
|
|
|
|
Assert.Equal(premiere.Id, seconde.Id);
|
|
Assert.Single(await _revues.ListerAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Une_revue_saisie_a_la_main_apprend_son_issn_au_premier_scan()
|
|
{
|
|
var aLaMain = await RevueAsync("Médor", issn: null);
|
|
|
|
var scannee = await RevueAsync("Médor");
|
|
|
|
Assert.Equal(aLaMain.Id, scannee.Id);
|
|
Assert.Equal("2466-6718", scannee.Issn);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Les_numeros_vivent_dans_la_revue_et_ne_font_pas_de_fiches()
|
|
{
|
|
var revue = await RevueAsync();
|
|
|
|
await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue
|
|
{
|
|
Numero = "43",
|
|
DateParution = new DateTime(2024, 6, 1, 0, 0, 0, DateTimeKind.Utc),
|
|
});
|
|
|
|
var apres = (await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue
|
|
{
|
|
Numero = "44",
|
|
DateParution = new DateTime(2024, 9, 1, 0, 0, 0, DateTimeKind.Utc),
|
|
})).Revue!;
|
|
|
|
Assert.Single(await _revues.ListerAsync());
|
|
|
|
// Du plus récent au plus ancien : sur un magazine, c'est le dernier reçu qu'on cherche.
|
|
Assert.Equal(["44", "43"], apres.Numeros.Select(n => n.Numero));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_numero_sans_numero_est_refuse()
|
|
{
|
|
var revue = await RevueAsync();
|
|
|
|
var resultat = await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue { Numero = " " });
|
|
|
|
Assert.NotNull(resultat.Erreur);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Le_meme_numero_ne_sajoute_pas_deux_fois()
|
|
{
|
|
var revue = await RevueAsync();
|
|
await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue { Numero = "43" });
|
|
|
|
var doublon = await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue { Numero = "43" });
|
|
|
|
Assert.NotNull(doublon.Erreur);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Un_numero_a_paraitre_est_refuse()
|
|
{
|
|
var revue = await RevueAsync();
|
|
|
|
var resultat = await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue
|
|
{
|
|
Numero = "99",
|
|
DateParution = DateTime.UtcNow.AddMonths(2),
|
|
});
|
|
|
|
Assert.NotNull(resultat.Erreur);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Les_numeros_sans_date_ferment_la_liste()
|
|
{
|
|
var revue = await RevueAsync();
|
|
await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue { Numero = "hors-série 1" });
|
|
|
|
var apres = (await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue
|
|
{
|
|
Numero = "43",
|
|
DateParution = new DateTime(2024, 6, 1, 0, 0, 0, DateTimeKind.Utc),
|
|
})).Revue!;
|
|
|
|
// Une date inconnue ne doit pas faire passer un numéro pour le plus ancien.
|
|
Assert.Equal(["43", "hors-série 1"], apres.Numeros.Select(n => n.Numero));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Supprimer_la_revue_emporte_ses_numeros()
|
|
{
|
|
var revue = await RevueAsync();
|
|
await _revues.AjouterNumeroAsync(revue.Id, new AjoutNumeroRevue { Numero = "43" });
|
|
|
|
Assert.True(await _revues.SupprimerAsync(revue.Id));
|
|
|
|
// Contrairement aux livres d'une série, un numéro n'existe que par sa revue.
|
|
Assert.Empty(await _db.NumerosRevue.ToListAsync());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Deux_revues_ne_partagent_ni_titre_ni_issn()
|
|
{
|
|
await RevueAsync();
|
|
var autre = await RevueAsync("Socialter", issn: "2270-4426");
|
|
|
|
var titrePris = await _revues.ModifierAsync(
|
|
autre.Id, new EnregistrementRevue { Titre = "médor" });
|
|
var issnPris = await _revues.ModifierAsync(
|
|
autre.Id, new EnregistrementRevue { Titre = "Socialter", Issn = "2466-6718" });
|
|
|
|
Assert.NotNull(titrePris.Erreur);
|
|
Assert.NotNull(issnPris.Erreur);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_db.Dispose();
|
|
_connexion.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|