diff --git a/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs b/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs new file mode 100644 index 0000000..a95bc15 --- /dev/null +++ b/src/ScadaLink.Commons/Interfaces/Services/IAuditWriter.cs @@ -0,0 +1,17 @@ +using ScadaLink.Commons.Entities.Audit; + +namespace ScadaLink.Commons.Interfaces.Services; + +/// +/// Boundary-side abstraction for emitting Audit Log (#23) events. +/// Implementations on the site write to local SQLite hot-path; on central they write to MS SQL directly. +/// Failures must NEVER abort the user-facing action. +/// +public interface IAuditWriter +{ + /// + /// Persist an audit event. Best-effort: implementations must swallow/log internal failures + /// rather than propagating them to the calling boundary code. + /// + Task WriteAsync(AuditEvent evt, CancellationToken ct = default); +} diff --git a/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs b/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs new file mode 100644 index 0000000..d91f534 --- /dev/null +++ b/src/ScadaLink.Commons/Interfaces/Services/ICentralAuditWriter.cs @@ -0,0 +1,16 @@ +using ScadaLink.Commons.Entities.Audit; + +namespace ScadaLink.Commons.Interfaces.Services; + +/// +/// Central-only audit writer for the direct-write path (Notification Outbox dispatch, Inbound API). +/// Distinct from so DI binding can differ between site and central hosts. +/// +public interface ICentralAuditWriter +{ + /// + /// Persist an audit event into the central AuditLog table directly (bypassing site telemetry). + /// Best-effort: implementations must swallow/log internal failures rather than propagating them. + /// + Task WriteAsync(AuditEvent evt, CancellationToken ct = default); +} diff --git a/tests/ScadaLink.Commons.Tests/Interfaces/Services/AuditWriterContractTests.cs b/tests/ScadaLink.Commons.Tests/Interfaces/Services/AuditWriterContractTests.cs new file mode 100644 index 0000000..c40a5d2 --- /dev/null +++ b/tests/ScadaLink.Commons.Tests/Interfaces/Services/AuditWriterContractTests.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using ScadaLink.Commons.Entities.Audit; +using ScadaLink.Commons.Interfaces.Services; + +namespace ScadaLink.Commons.Tests.Interfaces.Services; + +/// +/// Reflection-level contract guards for the Audit Log (#23) writer interfaces. +/// Locks in method signature so DI bindings + adapter implementations stay aligned. +/// +public class AuditWriterContractTests +{ + [Theory] + [InlineData(typeof(IAuditWriter))] + [InlineData(typeof(ICentralAuditWriter))] + public void WriteAsync_HasExpectedSignature(Type writerType) + { + var method = writerType.GetMethod("WriteAsync", BindingFlags.Instance | BindingFlags.Public); + Assert.NotNull(method); + Assert.Equal(typeof(Task), method!.ReturnType); + + var parameters = method.GetParameters(); + Assert.Equal(2, parameters.Length); + Assert.Equal(typeof(AuditEvent), parameters[0].ParameterType); + Assert.Equal(typeof(CancellationToken), parameters[1].ParameterType); + Assert.True(parameters[1].HasDefaultValue); + } + + [Fact] + public void IAuditWriter_AndICentralAuditWriter_AreDistinctTypes() + { + Assert.NotEqual(typeof(IAuditWriter), typeof(ICentralAuditWriter)); + Assert.False(typeof(IAuditWriter).IsAssignableFrom(typeof(ICentralAuditWriter))); + Assert.False(typeof(ICentralAuditWriter).IsAssignableFrom(typeof(IAuditWriter))); + } +}