feat(auditlog): IAuditPayloadFilter contract (#23 M5)
This commit is contained in:
30
src/ScadaLink.AuditLog/Payload/IAuditPayloadFilter.cs
Normal file
30
src/ScadaLink.AuditLog/Payload/IAuditPayloadFilter.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using ScadaLink.Commons.Entities.Audit;
|
||||
|
||||
namespace ScadaLink.AuditLog.Payload;
|
||||
|
||||
/// <summary>
|
||||
/// Filters an <see cref="AuditEvent"/> between construction and persistence —
|
||||
/// truncates oversized payload fields, applies header/body/SQL-parameter
|
||||
/// redaction, sets <see cref="AuditEvent.PayloadTruncated"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Pure function: returns a filtered COPY of the input via <c>with</c>
|
||||
/// expressions; never throws (over-redacts on internal failure and increments
|
||||
/// the <c>AuditRedactionFailure</c> health metric).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Wired in M5 between event construction and the writer chain
|
||||
/// (<c>FallbackAuditWriter.WriteAsync</c>, <c>CentralAuditWriter.WriteAsync</c>,
|
||||
/// and the <c>AuditLogIngestActor</c> handlers).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IAuditPayloadFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Apply the configured truncation + redaction policy to <paramref name="rawEvent"/>
|
||||
/// and return a filtered copy. MUST NOT throw — on internal failure, over-redact
|
||||
/// and surface the failure via the audit-redaction-failure health metric.
|
||||
/// </summary>
|
||||
AuditEvent Apply(AuditEvent rawEvent);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ScadaLink.AuditLog.Payload;
|
||||
using ScadaLink.Commons.Entities.Audit;
|
||||
|
||||
namespace ScadaLink.AuditLog.Tests.Payload;
|
||||
|
||||
/// <summary>
|
||||
/// Bundle A (M5-T1) contract test for <see cref="IAuditPayloadFilter"/>. The
|
||||
/// interface is the seam between event construction and writer persistence;
|
||||
/// later bundles register the production implementation as a singleton and
|
||||
/// invoke it from the site/central writer paths. We pin the surface area here
|
||||
/// via reflection so accidental signature drift breaks the build before the
|
||||
/// downstream wiring goes red.
|
||||
/// </summary>
|
||||
public class PayloadFilterContractTests
|
||||
{
|
||||
[Fact]
|
||||
public void Interface_Exists_InPayloadNamespace()
|
||||
{
|
||||
var type = typeof(IAuditPayloadFilter);
|
||||
|
||||
Assert.True(type.IsInterface, "IAuditPayloadFilter must be an interface");
|
||||
Assert.Equal("ScadaLink.AuditLog.Payload", type.Namespace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Apply_Method_HasDocumentedSignature()
|
||||
{
|
||||
var type = typeof(IAuditPayloadFilter);
|
||||
|
||||
var method = type.GetMethod(
|
||||
"Apply",
|
||||
BindingFlags.Instance | BindingFlags.Public,
|
||||
binder: null,
|
||||
types: new[] { typeof(AuditEvent) },
|
||||
modifiers: null);
|
||||
|
||||
Assert.NotNull(method);
|
||||
Assert.Equal(typeof(AuditEvent), method!.ReturnType);
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
Assert.Single(parameters);
|
||||
Assert.Equal("rawEvent", parameters[0].Name);
|
||||
Assert.Equal(typeof(AuditEvent), parameters[0].ParameterType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Interface_DeclaresExactlyOneMethod()
|
||||
{
|
||||
var type = typeof(IAuditPayloadFilter);
|
||||
var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(m => !m.IsSpecialName)
|
||||
.ToArray();
|
||||
|
||||
Assert.Single(methods);
|
||||
Assert.Equal("Apply", methods[0].Name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user