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:
@@ -40,23 +40,17 @@ public class CleOeuvreTests
|
||||
CleOeuvre.Cle("Nouvelles : second recueil"));
|
||||
|
||||
/// <summary>
|
||||
/// Limite <b>héritée</b> de <see cref="NormalisationTexte"/>, pas propre à la clé d'œuvre :
|
||||
/// la ligature « œ » n'est pas ramenée à « oe ».
|
||||
/// La ligature « œ » est ramenée à « oe » — corrigé le 2026-08-19.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <c>NormalisationTexte</c> décompose en NFD, qui sépare les accents mais laisse les
|
||||
/// ligatures intactes — seul NFKD les défait. Conséquence concrète : <i>L'Œuvre</i> de Zola,
|
||||
/// que la BnF écrit avec la ligature, n'est pas rapproché d'un « L'oeuvre » saisi au clavier.
|
||||
/// <para>
|
||||
/// Le défaut ne vient pas d'ici et ne se corrige pas ici : la même fonction alimente la
|
||||
/// recherche du catalogue et la clé unique des auteurs, et en changer déplacerait ces deux
|
||||
/// invariants. Consigné dans IDEES.md ; ce test existe pour que la limite soit visible plutôt
|
||||
/// que découverte en usage.
|
||||
/// </para>
|
||||
/// Le comportement vient de <see cref="NormalisationTexte"/> et non d'ici : la décomposition
|
||||
/// NFD sépare les accents mais laisse les ligatures intactes, d'où une table explicite.
|
||||
/// Sans elle, <i>L'Œuvre</i> de Zola — que la BnF écrit avec la ligature — restait
|
||||
/// introuvable à qui tape « oeuvre » au clavier, et n'était pas grisé dans sa bibliographie.
|
||||
/// </remarks>
|
||||
[Fact]
|
||||
public void Ne_reduit_pas_la_ligature_oe() =>
|
||||
Assert.NotEqual(CleOeuvre.Cle("L'Œuvre"), CleOeuvre.Cle("L'oeuvre"));
|
||||
public void Reduit_la_ligature_oe() =>
|
||||
Assert.Equal(CleOeuvre.Cle("L'Œuvre"), CleOeuvre.Cle("L'oeuvre"));
|
||||
|
||||
/// <summary>
|
||||
/// Limite assumée : un titre retraduit n'est pas rapproché du tout.
|
||||
|
||||
@@ -106,6 +106,12 @@ public class RapprochementAuteursTests
|
||||
[InlineData(" Zola, Émile ", "zola emile")]
|
||||
[InlineData("Saint-Exupéry", "saint exupery")]
|
||||
[InlineData("L'Écume", "l ecume")]
|
||||
// Ligatures : NFD ne les défait pas, une table explicite s'en charge (2026-08-19).
|
||||
[InlineData("L'Œuvre", "l oeuvre")]
|
||||
[InlineData("Sœur Emmanuelle", "soeur emmanuelle")]
|
||||
[InlineData("Ex æquo", "ex aequo")]
|
||||
// La ligature vaut deux lettres, elle ne coupe donc PAS le mot en deux.
|
||||
[InlineData("cœur", "coeur")]
|
||||
[InlineData("", "")]
|
||||
[InlineData(null, "")]
|
||||
public void La_normalisation_met_a_plat_casse_accents_et_ponctuation(string? brut, string attendu)
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user