169 lines
6.7 KiB
C#
169 lines
6.7 KiB
C#
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");
|
|
}
|
|
}
|