Ramener les ligatures « œ » et « æ » à « oe » et « ae »

L'Œuvre de Zola, que la BnF écrit avec la ligature, était introuvable à
qui tape « oeuvre » : NFD sépare les accents mais laisse les ligatures.

Table explicite plutôt que NFKD, qui aurait aussi transformé les exposants,
les chiffres romains et les espaces insécables. Une normalisation de
recherche doit rester prévisible.

Sept colonnes normalisées en dépendent, dont cinq portent une unicité. Le
rattrapage au démarrage est donc étendu aux envies, séries, revues et
numéros — qui ne calculaient leurs formes qu'à l'écriture — et refuse toute
collision AVANT d'écrire : sinon l'exception tomberait au démarrage et le
serveur ne se lancerait plus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-19 22:25:59 +02:00
co-authored by Claude Opus 5
parent ca0a9dc18c
commit b97cf33494
7 changed files with 293 additions and 36 deletions
@@ -186,6 +186,89 @@ public class ServiceRenormalisationTests : IDisposable
Assert.Empty(_db.Livres);
}
// ── Rattrapage des tables plus récentes ─────────────────────────────────
//
// Envies, séries, revues et numéros calculent leurs formes À L'ÉCRITURE : sans ce passage,
// un changement de règle de normalisation — comme le développement des ligatures le
// 2026-08-19 — laisserait ces lignes sur leur ancienne clé, donc introuvables par la
// nouvelle. Trois de ces colonnes portent en plus une UNICITÉ, et une collision se paierait
// au démarrage du serveur.
[Fact]
public async Task Une_envie_ecrite_avant_le_changement_de_regle_est_rattrapee()
{
// Forme telle qu'elle aurait été calculée avant le développement des ligatures.
_db.LivresSouhaites.Add(new LivreSouhaite
{
Utilisateur = "mathieu",
Titre = "L'Œuvre",
TitreNormalise = "l œuvre",
Auteur = "Émile Zola",
AuteurNormalise = "emile zola",
});
await _db.SaveChangesAsync();
await ExecuterAsync();
Assert.Equal("l oeuvre", _db.LivresSouhaites.Single().TitreNormalise);
}
[Fact]
public async Task Une_serie_et_une_revue_sont_rattrapees_aussi()
{
_db.Series.Add(new Serie { Titre = "Les Sœurs", TitreNormalise = "les sœurs" });
_db.Revues.Add(new Revue { Titre = "Cœur", TitreNormalise = "cœur" });
await _db.SaveChangesAsync();
await ExecuterAsync();
Assert.Equal("les soeurs", _db.Series.Single().TitreNormalise);
Assert.Equal("coeur", _db.Revues.Single().TitreNormalise);
}
/// <summary>
/// ⚠️ Le cas qui empêcherait le serveur de démarrer.
/// </summary>
/// <remarks>
/// La nouvelle règle rend identiques deux séries qui ne l'étaient pas. L'index unique
/// refuserait l'écriture, et l'exception surviendrait <b>au démarrage</b> : l'application ne
/// se lancerait plus du tout. La plus ancienne prend la nouvelle forme, la suivante garde
/// l'ancienne — mal normalisée vaut infiniment mieux que supprimée, ou qu'un serveur mort.
/// </remarks>
[Fact]
public async Task Une_collision_dunicite_ne_fait_pas_echouer_le_demarrage()
{
_db.Series.Add(new Serie { Titre = "L'Œuvre", TitreNormalise = "l œuvre" });
_db.Series.Add(new Serie { Titre = "L'oeuvre", TitreNormalise = "l oeuvre" });
await _db.SaveChangesAsync();
await ExecuterAsync();
var series = _db.Series.OrderBy(s => s.Id).ToList();
Assert.Equal(2, series.Count);
Assert.Equal(["l oeuvre", "l œuvre"], series.Select(s => s.TitreNormalise).Order());
}
/// <summary>
/// Une ligne déjà correcte ne doit pas se bloquer elle-même.
/// </summary>
/// <remarks>
/// Le jeu des clés occupées part de l'état actuel de la base : sans la libération de sa
/// propre clé, toute ligne dont la forme change verrait sa nouvelle valeur « déjà prise »
/// dès qu'elle est égale à son ancienne — et rien ne serait jamais corrigé.
/// </remarks>
[Fact]
public async Task Le_rattrapage_reste_idempotent()
{
_db.Revues.Add(new Revue { Titre = "Cœur", TitreNormalise = "cœur" });
await _db.SaveChangesAsync();
await ExecuterAsync();
await ExecuterAsync();
Assert.Equal("coeur", _db.Revues.Single().TitreNormalise);
}
public void Dispose()
{
_db.Dispose();