Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests/AuditWriterActorTests.cs
T
Joseph Doherty 64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
docs: backfill XML documentation across 756 files
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public
members surfaced by commentchecker — resolves 5,847 of 5,869 issues
(99.6%) across three /fixdocs passes.
2026-05-28 08:10:17 -04:00

106 lines
3.8 KiB
C#

using Akka.Actor;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Audit;
using ZB.MOM.WW.OtOpcUa.Commons.Types;
using ZB.MOM.WW.OtOpcUa.ControlPlane.Audit;
using ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Harness;
namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests;
public sealed class AuditWriterActorTests : ControlPlaneActorTestBase
{
private static AuditEvent NewEvent(Guid eventId, string action = "Edit", string actor = "joe") =>
new(
eventId,
"Config",
action,
actor,
DateTime.UtcNow,
DetailsJson: "{\"field\":\"value\"}",
SourceNode: NodeId.Parse("node-a"),
CorrelationId: CorrelationId.NewId());
/// <summary>Verifies that buffered events flush when count threshold is reached.</summary>
[Fact]
public void Buffered_events_flush_on_count_threshold()
{
var dbFactory = NewInMemoryDbFactory();
var actor = Sys.ActorOf(AuditWriterActor.Props(dbFactory));
// Sending exactly FlushBatchSize events triggers a flush.
for (var i = 0; i < AuditWriterActor.FlushBatchSize; i++)
actor.Tell(NewEvent(Guid.NewGuid()));
// Give the actor a beat to process the messages.
AwaitAssert(() =>
{
using var db = dbFactory.CreateDbContext();
db.ConfigAuditLogs.Count().ShouldBe(AuditWriterActor.FlushBatchSize);
}, duration: TimeSpan.FromSeconds(2));
}
/// <summary>Verifies that duplicate event IDs within a batch are deduplicated in the buffer.</summary>
[Fact]
public void Duplicate_eventIds_within_a_batch_dedup_in_buffer()
{
var dbFactory = NewInMemoryDbFactory();
var actor = Sys.ActorOf(AuditWriterActor.Props(dbFactory));
// Send 1000 messages, but only 100 unique EventIds (10x duplication).
var uniqueIds = Enumerable.Range(0, 100).Select(_ => Guid.NewGuid()).ToArray();
for (var i = 0; i < 1000; i++)
actor.Tell(NewEvent(uniqueIds[i % 100]));
// Force a flush — send PoisonPill, which triggers PostStop → FlushBuffer.
Watch(actor);
actor.Tell(PoisonPill.Instance);
ExpectTerminated(actor);
using var db = dbFactory.CreateDbContext();
db.ConfigAuditLogs.Count().ShouldBe(100, "in-buffer dedup should collapse duplicate EventIds");
}
/// <summary>Verifies that PostStop flushes the pending buffer.</summary>
[Fact]
public void PostStop_flushes_pending_buffer()
{
var dbFactory = NewInMemoryDbFactory();
var actor = Sys.ActorOf(AuditWriterActor.Props(dbFactory));
// 10 events — well below the threshold, so they sit in-buffer.
for (var i = 0; i < 10; i++)
actor.Tell(NewEvent(Guid.NewGuid()));
Watch(actor);
actor.Tell(PoisonPill.Instance);
ExpectTerminated(actor);
using var db = dbFactory.CreateDbContext();
db.ConfigAuditLogs.Count().ShouldBe(10);
}
/// <summary>Verifies that EventId and CorrelationId are persisted to dedicated columns.</summary>
[Fact]
public void EventId_and_CorrelationId_are_persisted_to_dedicated_columns()
{
var dbFactory = NewInMemoryDbFactory();
var actor = Sys.ActorOf(AuditWriterActor.Props(dbFactory));
var eventId = Guid.NewGuid();
actor.Tell(NewEvent(eventId));
Watch(actor);
actor.Tell(PoisonPill.Instance);
ExpectTerminated(actor);
using var db = dbFactory.CreateDbContext();
var row = db.ConfigAuditLogs.Single();
row.EventId.ShouldBe(eventId);
row.CorrelationId.ShouldNotBeNull();
row.DetailsJson.ShouldBe("{\"field\":\"value\"}");
row.EventType.ShouldBe("Config:Edit");
row.NodeId.ShouldBe("node-a");
}
}