Fond les quinze migrations en une seule
Decide avec l'utilisateur avant la sortie du projet, en acceptant de repartir d'une base vierge en production. ⚠ Aucune base anterieure n'est plus migrable : __EFMigrationsHistory d'une installation existante contient les quinze anciens noms, et EF Core tenterait de recreer des tables deja presentes. La mise a jour echouerait, et son retour arriere aussi. La seule voie sur un serveur installe est remove --purge puis reinstallation — sans le --purge, data_dir survit et l'ancienne base revient avec son historique incompatible. La fusion est verifiee, pas supposee : les deux schemas ont ete appliques sur deux bases SQLite vierges puis compares. 97 colonnes identiques, 27 index identiques, 11 cles etrangeres identiques avec leurs modes ON DELETE. Les deux SET NULL, le RESTRICT des auteurs, les trois index uniques partiels et les deux index qui cohabitent sur Prets.LivreId sont tous preserves. Deux ecarts subsistent, sans effet : l'ordre physique des colonnes, et les DEFAULT 0 de Role, TypeDocument et Rang, qui n'existaient que pour remplir les lignes deja presentes lors d'un AddColumn. Deux SQL de reprise de donnees disparaissent avec leurs migrations (StatutPersonnelEtTableAuteurs, RangDesEnvies). MigrationRangDesEnviesTests, qui migrait jusqu'a ListeDEnvies pour eprouver le second, n'a plus d'objet : 628 tests -> 626. Verifie en execution sur une base creee de zero : creation d'un livre, recherche sans accents, pret, et second pret refuse par l'index unique partiel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,111 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// La migration <c>RangDesEnvies</c>, appliquée pour de vrai à une base déjà peuplée.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Ce test ne double pas <c>ServiceSouhaitsTests</c> : celui-ci crée son schéma avec
|
||||
/// <c>EnsureCreated</c>, qui <b>ne joue aucune migration</b>. Le remplissage de la colonne
|
||||
/// <c>Rang</c> 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.
|
||||
/// </remarks>
|
||||
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<MaBibliDbContext>().UseSqlite(_connexion).Options);
|
||||
}
|
||||
|
||||
private IMigrator Migrateur => _db.GetInfrastructure().GetRequiredService<IMigrator>();
|
||||
|
||||
/// <summary>
|
||||
/// Le cœur du test : la migration doit reconduire <b>l'ordre affiché jusqu'ici</b> —
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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"));
|
||||
}
|
||||
|
||||
/// <summary>Une base sans aucune envie doit traverser la migration sans broncher.</summary>
|
||||
[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<List<(int Rang, string Titre)>> 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<MaBibliDbContext>().UseSqlite(_connexion).Options);
|
||||
|
||||
return await db.LivresSouhaites
|
||||
.Where(s => s.Utilisateur == utilisateur)
|
||||
.OrderBy(s => s.Rang)
|
||||
.Select(s => new ValueTuple<int, string>(s.Rang, s.Titre))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_db.Dispose();
|
||||
_connexion.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user