AuditedTypes.Contains(entry.Entity.GetType()) exigeait l'egalite exacte de type, or Section est abstraite : le type runtime est toujours SectionMap, SectionQuiz... Aucune des 13 sortes de section n'etait journalisee -- soit precisement ce que l'ecran d'audit livre le 12/08 devait tracer. Resource, Configuration, Device, User et Instance, eux, passaient : ils sont concrets, et c'est ce qui rendait le trou invisible. Remplace par une remontee a la classe de base auditee (IsInstanceOfType). EntityType porte « Section », pas « SectionMap ». Des deux sorties possibles, c'est la normalisation cote serveur qui est retenue : - le filtre « Section » est deja dans l'ecran et se met a rendre des lignes sans toucher manager-app, donc sans coordonner deux repos ; - elargir le filtre front aurait coute 13 entrees de liste et 39 cles i18n, et surtout aurait laisse tout futur sous-type sortir du filtre en silence -- la meme classe de panne que celle qu'on ferme ici ; - le sous-type concret n'est pas perdu : le discriminateur TPH est une propriete du modele, donc serialisee dans NewValues (« Discriminator »: « Article »). Un test le tient. 8 tests, dont un par reflexion qui affirme que les 13 sous-types concrets resolvent bien vers Section : la liste de types d'origine n'obligeait personne a la suivre, c'est ce qui l'a laissee devenir fausse. dotnet test 171/171. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
165 lines
5.5 KiB
C#
165 lines
5.5 KiB
C#
using Manager.DTOs;
|
|
using ManagerService.Data;
|
|
using ManagerService.Data.SubSection;
|
|
using ManagerService.DTOs;
|
|
using ManagerService.Tests.Infrastructure;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text.Json;
|
|
using Xunit;
|
|
|
|
namespace ManagerService.Tests.Data
|
|
{
|
|
/// <summary>
|
|
/// Le journal d'audit ne voyait aucune section : le filtre exigeait l'égalité exacte
|
|
/// de type, or <see cref="Section"/> est abstraite. Ces tests tiennent l'invariant —
|
|
/// un sous-type est journalisé, et il l'est sous le nom « Section ».
|
|
/// </summary>
|
|
public class AuditLogTests
|
|
{
|
|
private static SectionArticle NewArticle(string id = "s1") =>
|
|
TestSection.Article(id, "i1", "Label", "c1");
|
|
|
|
[Fact]
|
|
public void SectionSubType_IsAudited()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
|
|
db.Sections.Add(NewArticle());
|
|
db.SaveChanges();
|
|
|
|
var log = Assert.Single(db.AuditLogs.ToList());
|
|
Assert.Equal("Create", log.Action);
|
|
Assert.Equal("s1", log.EntityId);
|
|
Assert.Equal("i1", log.InstanceId);
|
|
}
|
|
|
|
[Fact]
|
|
public void SectionSubType_IsLoggedUnderBaseTypeName()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
|
|
db.Sections.Add(NewArticle());
|
|
db.SaveChanges();
|
|
|
|
// Le filtre « Section » de l'écran d'audit interroge ce nom : s'il portait
|
|
// « SectionArticle », l'écran ne rendrait toujours aucune ligne.
|
|
Assert.Equal("Section", db.AuditLogs.Single().EntityType);
|
|
}
|
|
|
|
[Fact]
|
|
public void SectionSubType_KeepsConcreteTypeInValues()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
|
|
db.Sections.Add(NewArticle());
|
|
db.SaveChanges();
|
|
|
|
// Normaliser EntityType ne perd pas le sous-type : le discriminateur TPH est
|
|
// une propriété du modèle, donc sérialisée avec les autres.
|
|
var values = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(
|
|
db.AuditLogs.Single().NewValues!);
|
|
Assert.Equal("Article", values!["Discriminator"].GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public void SectionUpdate_RecordsOnlyChangedFields()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Sections.Add(NewArticle());
|
|
db.SaveChanges();
|
|
|
|
var section = db.Sections.Single();
|
|
section.Label = "Renommée";
|
|
db.SaveChanges();
|
|
|
|
var update = db.AuditLogs.Single(a => a.Action == "Update");
|
|
var oldValues = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(update.OldValues!);
|
|
var newValues = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(update.NewValues!);
|
|
|
|
Assert.Equal("Label", Assert.Single(newValues!.Keys.Where(k => k != "DateUpdate")));
|
|
Assert.Equal("Label", oldValues!["Label"].GetString());
|
|
Assert.Equal("Renommée", newValues["Label"].GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public void SectionDelete_IsAudited()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
db.Sections.Add(NewArticle());
|
|
db.SaveChanges();
|
|
|
|
db.Sections.Remove(db.Sections.Single());
|
|
db.SaveChanges();
|
|
|
|
var delete = db.AuditLogs.Single(a => a.Action == "Delete");
|
|
Assert.Equal("Section", delete.EntityType);
|
|
Assert.Equal("s1", delete.EntityId);
|
|
}
|
|
|
|
[Fact]
|
|
public void NonAuditedEntity_IsNotLogged()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
|
|
db.VisitEvents.Add(new VisitEvent
|
|
{
|
|
Id = "v1",
|
|
InstanceId = "i1",
|
|
SessionId = "sess",
|
|
EventType = VisitEventType.SectionView,
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
db.SaveChanges();
|
|
|
|
Assert.Empty(db.AuditLogs.ToList());
|
|
}
|
|
|
|
[Fact]
|
|
public void OtherAuditedTypes_KeepTheirOwnName()
|
|
{
|
|
using var db = DbContextFactory.Create();
|
|
|
|
db.Users.Add(new User
|
|
{
|
|
Id = "u1",
|
|
InstanceId = "i1",
|
|
Email = "a@b.c",
|
|
FirstName = "A",
|
|
LastName = "B",
|
|
Password = "x",
|
|
Token = "t"
|
|
});
|
|
db.SaveChanges();
|
|
|
|
Assert.Equal("User", db.AuditLogs.Single().EntityType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Le trou d'origine venait d'une liste de types que rien n'obligeait à suivre les
|
|
/// sous-classes. Par réflexion : tout sous-type concret de Section doit être journalisé.
|
|
/// </summary>
|
|
[Fact]
|
|
public void EverySectionSubType_ResolvesToSection()
|
|
{
|
|
var subTypes = typeof(Section).Assembly.GetTypes()
|
|
.Where(t => typeof(Section).IsAssignableFrom(t) && !t.IsAbstract)
|
|
.ToList();
|
|
|
|
Assert.Equal(13, subTypes.Count);
|
|
|
|
var auditedTypeOf = typeof(MyInfoMateDbContext)
|
|
.GetMethod("AuditedTypeOf", BindingFlags.NonPublic | BindingFlags.Static)!;
|
|
|
|
foreach (var subType in subTypes)
|
|
{
|
|
var instance = System.Runtime.CompilerServices.RuntimeHelpers
|
|
.GetUninitializedObject(subType);
|
|
Assert.Equal(typeof(Section), auditedTypeOf.Invoke(null, new[] { instance }));
|
|
}
|
|
}
|
|
}
|
|
}
|