using MaBibli.Api.Data; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace MaBibli.Tests; /// /// La migration RangDesEnvies, appliquée pour de vrai à une base déjà peuplée. /// /// /// Ce test ne double pas ServiceSouhaitsTests : celui-ci crée son schéma avec /// EnsureCreated, qui ne joue aucune migration. Le remplissage de la colonne /// Rang n'y passerait donc jamais, et une erreur dans son SQL ne se verrait qu'en /// production — sur les listes d'envies réelles, qui se réordonneraient sous les yeux de leur /// propriétaire. /// public class MigrationRangDesEnviesTests : IDisposable { private const string AvantLeRang = "20260818052834_ListeDEnvies"; private readonly SqliteConnection _connexion; private readonly MaBibliDbContext _db; public MigrationRangDesEnviesTests() { // Connexion maintenue ouverte : une base SQLite « :memory: » disparaît avec elle. _connexion = new SqliteConnection("Data Source=:memory:"); _connexion.Open(); _db = new MaBibliDbContext( new DbContextOptionsBuilder().UseSqlite(_connexion).Options); } private IMigrator Migrateur => _db.GetInfrastructure().GetRequiredService(); /// /// Le cœur du test : la migration doit reconduire l'ordre affiché jusqu'ici — /// auteurs renseignés d'abord, puis par auteur, puis par titre — et repartir de zéro pour /// chaque utilisateur, le rang étant personnel comme le reste de la table. /// [Fact] public async Task Reconduit_lancien_ordre_daffichage_utilisateur_par_utilisateur() { await Migrateur.MigrateAsync(AvantLeRang); // Insertion en SQL brut : à ce point du schéma, la colonne Rang n'existe pas encore, // et l'entité C# la porte déjà. Passer par EF échouerait. await ExecuterAsync(""" INSERT INTO LivresSouhaites (Id, Utilisateur, Titre, TitreNormalise, Auteur, AuteurNormalise, DateAjout) VALUES (1, 'mathieu', 'Germinal', 'germinal', 'Émile Zola', 'emile zola', '2026-01-01'), (2, 'mathieu', 'Sans nom', 'sans nom', NULL, '', '2026-01-02'), (3, 'mathieu', 'Au bonheur', 'au bonheur', 'Émile Zola', 'emile zola', '2026-01-03'), (4, 'mathieu', 'Les fourmis','les fourmis','Bernard Werber', 'bernard werber', '2026-01-04'), (5, 'camille', 'Sa liste', 'sa liste', 'Bernard Werber', 'bernard werber', '2026-01-05'), (6, 'camille', 'Sans nom', 'sans nom', NULL, '', '2026-01-06'); """); await Migrateur.MigrateAsync(); // Werber avant Zola, « Au bonheur » avant « Germinal », et l'envie sans auteur en // dernier : exactement ce que la liste montrait avant la colonne. Assert.Equal( [(0, "Les fourmis"), (1, "Au bonheur"), (2, "Germinal"), (3, "Sans nom")], await RangsAsync("mathieu")); // ⚠️ La numérotation de Camille repart de zéro : le rang n'a de sens qu'à l'intérieur // d'une liste. Continuer celle de Mathieu ferait dépendre l'ordre de l'un de l'autre. Assert.Equal([(0, "Sa liste"), (1, "Sans nom")], await RangsAsync("camille")); } /// Une base sans aucune envie doit traverser la migration sans broncher. [Fact] public async Task Sapplique_aussi_a_une_base_vide() { await Migrateur.MigrateAsync(); Assert.Empty(await _db.LivresSouhaites.ToListAsync()); } private async Task ExecuterAsync(string sql) { await using var commande = _connexion.CreateCommand(); commande.CommandText = sql; await commande.ExecuteNonQueryAsync(); } private async Task> RangsAsync(string utilisateur) { // Contexte neuf : le précédent a pu mettre en cache le schéma d'avant la migration. await using var db = new MaBibliDbContext( new DbContextOptionsBuilder().UseSqlite(_connexion).Options); return await db.LivresSouhaites .Where(s => s.Utilisateur == utilisateur) .OrderBy(s => s.Rang) .Select(s => new ValueTuple(s.Rang, s.Titre)) .ToListAsync(); } public void Dispose() { _db.Dispose(); _connexion.Dispose(); GC.SuppressFinalize(this); } }