Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
using Omika.Diagnostics.Referential;
|
||||
using Omika.Diagnostics.Tests.Fakes;
|
||||
|
||||
namespace Omika.Diagnostics.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// T24-A Partie B — référentiel administrable : normalisation TRIM+UPPER, unicité
|
||||
/// (type, code), code immuable, archivage sans DELETE, libellés et codes non référencés.
|
||||
/// S'appuie sur le <see cref="FakeDiagnosticReferenceRepository"/> (même contrat que le
|
||||
/// repository Npgsql).
|
||||
/// </summary>
|
||||
public class DiagnosticReferenceTests
|
||||
{
|
||||
private static DiagnosticReferenceRecord Ref(
|
||||
long id,
|
||||
string type,
|
||||
string code,
|
||||
string label,
|
||||
bool enabled = true,
|
||||
int sortOrder = 0) => new()
|
||||
{
|
||||
Id = id,
|
||||
ReferenceType = type,
|
||||
Code = code,
|
||||
Label = label,
|
||||
Enabled = enabled,
|
||||
SortOrder = sortOrder,
|
||||
CreatedAtUtc = DateTimeOffset.UtcNow,
|
||||
UpdatedAtUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
// ------------------------------------------- normalisation ----
|
||||
[Theory]
|
||||
[InlineData("agenda", "AGENDA")]
|
||||
[InlineData(" agenda ", "AGENDA")]
|
||||
[InlineData("Facture_Detail", "FACTURE_DETAIL")]
|
||||
[InlineData(null, null)]
|
||||
[InlineData("", null)]
|
||||
[InlineData(" ", null)]
|
||||
public void NormalizeCode_TrimsAndUppers(string? input, string? expected)
|
||||
{
|
||||
Assert.Equal(expected, DiagnosticReferenceNormalization.NormalizeCode(input));
|
||||
}
|
||||
|
||||
// ------------------------------------------- création + normalisation ----
|
||||
[Fact]
|
||||
public async Task Create_NormalizesCodeAndLabel()
|
||||
{
|
||||
var repo = new FakeDiagnosticReferenceRepository();
|
||||
|
||||
DiagnosticReferenceRecord? created = await repo.CreateAsync(
|
||||
DiagnosticReferenceTypes.ScreenCode, " facture_detail ", " Détail de facture ", null, true, 0);
|
||||
|
||||
Assert.NotNull(created);
|
||||
Assert.Equal("FACTURE_DETAIL", created!.Code);
|
||||
Assert.Equal("Détail de facture", created.Label);
|
||||
}
|
||||
|
||||
// ------------------------------------------- unicité (type, code) ----
|
||||
[Fact]
|
||||
public async Task Create_DuplicateTypeCode_CaseInsensitive_ReturnsNull()
|
||||
{
|
||||
var repo = new FakeDiagnosticReferenceRepository();
|
||||
repo.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"));
|
||||
|
||||
DiagnosticReferenceRecord? duplicate = await repo.CreateAsync(
|
||||
DiagnosticReferenceTypes.ScreenCode, "agenda", "Agenda bis", null, true, 0);
|
||||
|
||||
Assert.Null(duplicate);
|
||||
Assert.Single(await repo.ListAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_SameCodeDifferentType_IsAllowed()
|
||||
{
|
||||
var repo = new FakeDiagnosticReferenceRepository();
|
||||
repo.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"));
|
||||
|
||||
// Même code mais type différent → autorisé (unicité = type + code).
|
||||
DiagnosticReferenceRecord? feature = await repo.CreateAsync(
|
||||
DiagnosticReferenceTypes.FeatureCode, "agenda", "Agenda (fonctionnalité)", null, true, 0);
|
||||
|
||||
Assert.NotNull(feature);
|
||||
Assert.Equal(2, (await repo.ListAsync()).Count);
|
||||
}
|
||||
|
||||
// ------------------------------------------- type invalide ----
|
||||
[Fact]
|
||||
public async Task Create_UnsupportedType_ReturnsNull()
|
||||
{
|
||||
var repo = new FakeDiagnosticReferenceRepository();
|
||||
|
||||
DiagnosticReferenceRecord? created = await repo.CreateAsync(
|
||||
"EVENT_CODE", "X", "Libellé", null, true, 0);
|
||||
|
||||
Assert.Null(created);
|
||||
}
|
||||
|
||||
// ------------------------------------------- code immuable ----
|
||||
[Fact]
|
||||
public async Task Update_ChangesLabelAndState_ButNeverCode()
|
||||
{
|
||||
var repo = new FakeDiagnosticReferenceRepository();
|
||||
repo.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"));
|
||||
|
||||
bool updated = await repo.UpdateAsync(1, "Nouvel Agenda", "Description", false, 7);
|
||||
|
||||
Assert.True(updated);
|
||||
DiagnosticReferenceRecord? record = await repo.GetByIdAsync(1);
|
||||
Assert.NotNull(record);
|
||||
Assert.Equal("AGENDA", record!.Code); // code immuable.
|
||||
Assert.Equal("Nouvel Agenda", record.Label);
|
||||
Assert.False(record.Enabled);
|
||||
Assert.Equal(7, record.SortOrder);
|
||||
}
|
||||
|
||||
// ------------------------------------------- archivage sans DELETE ----
|
||||
[Fact]
|
||||
public async Task Archive_DisablesWithoutDelete()
|
||||
{
|
||||
var repo = new FakeDiagnosticReferenceRepository();
|
||||
repo.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"));
|
||||
|
||||
bool updated = await repo.UpdateAsync(1, "Agenda", null, enabled: false, sortOrder: 0);
|
||||
|
||||
Assert.True(updated);
|
||||
Assert.False((await repo.GetByIdAsync(1))!.Enabled);
|
||||
// Aucun DELETE : l'entrée reste listée (état Archivé affiché, jamais masqué).
|
||||
Assert.Single(await repo.ListAsync());
|
||||
}
|
||||
|
||||
// ------------------------------------------- libellés dropdown ----
|
||||
[Fact]
|
||||
public async Task GetLabels_ReturnsCodeToLabelIncludingArchived()
|
||||
{
|
||||
var repo = new FakeDiagnosticReferenceRepository();
|
||||
repo.Seed(
|
||||
Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"),
|
||||
Ref(2, DiagnosticReferenceTypes.ScreenCode, "SITE_LIST", "Sites", enabled: false),
|
||||
Ref(3, DiagnosticReferenceTypes.FeatureCode, "EMAIL_SEND", "Envoi de courriel"));
|
||||
|
||||
IReadOnlyDictionary<string, string> labels = await repo.GetLabelsAsync(DiagnosticReferenceTypes.ScreenCode);
|
||||
|
||||
Assert.Equal("Agenda", labels["AGENDA"]);
|
||||
Assert.Equal("Sites", labels["SITE_LIST"]); // archivé inclus (libellé historique utile).
|
||||
Assert.False(labels.ContainsKey("EMAIL_SEND"));
|
||||
}
|
||||
|
||||
// ------------------------------------------- codes non référencés ----
|
||||
[Fact]
|
||||
public async Task GetUnreferenced_ReturnsObservedWithoutReference()
|
||||
{
|
||||
var repo = new FakeDiagnosticReferenceRepository();
|
||||
repo.Seed(Ref(1, DiagnosticReferenceTypes.ScreenCode, "AGENDA", "Agenda"));
|
||||
DateTimeOffset last = DateTimeOffset.UtcNow.AddDays(-1);
|
||||
|
||||
repo.SeedObserved(DiagnosticReferenceTypes.ScreenCode, "AGENDA", 40, last); // référencé → exclu.
|
||||
repo.SeedObserved(DiagnosticReferenceTypes.ScreenCode, "FACTURE_DETAIL", 128, last); // non référencé.
|
||||
repo.SeedObserved(DiagnosticReferenceTypes.FeatureCode, "FACTURE_EXPORT_PDF", 12, last);
|
||||
|
||||
IReadOnlyList<DiagnosticUnreferencedCode> unreferenced = await repo.GetUnreferencedAsync(100);
|
||||
|
||||
Assert.Equal(2, unreferenced.Count);
|
||||
Assert.Contains(unreferenced, u => u.ReferenceType == DiagnosticReferenceTypes.ScreenCode && u.Code == "FACTURE_DETAIL");
|
||||
Assert.Contains(unreferenced, u => u.ReferenceType == DiagnosticReferenceTypes.FeatureCode && u.Code == "FACTURE_EXPORT_PDF");
|
||||
Assert.DoesNotContain(unreferenced, u => u.Code == "AGENDA");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user