using Akka.Actor;
using Akka.TestKit.Xunit2;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.SecuredWrites;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
namespace ZB.MOM.WW.ScadaBridge.ManagementService.Tests;
///
/// Review 2026-10-21 08/G3: terminal PendingSecuredWrites rows were never purged.
/// The Secured Writes list sweep (which already expires overdue Pending rows) now also
/// purges decided/executed rows older than SecuredWriteTerminalRetention.
///
public class SecuredWriteTerminalPurgeTests : TestKit
{
private readonly ISecuredWriteRepository _repo = Substitute.For();
private readonly ServiceCollection _services = new ServiceCollection().AddTestConfigurationTransaction();
/// Initializes the harness with an empty secured-write table.
public SecuredWriteTerminalPurgeTests()
{
_services.AddScoped(_ => _repo);
_repo.QueryAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any(),
Arg.Any?>(), Arg.Any())
.Returns(Array.Empty());
}
private void UseOptions(ManagementServiceOptions options) =>
_services.AddSingleton>(Options.Create(options));
private IActorRef CreateActor() =>
Sys.ActorOf(Props.Create(() => new ManagementActor(
_services.BuildServiceProvider(), NullLogger.Instance)));
private static ManagementEnvelope List() =>
new(new AuthenticatedUser("admin", "Admin", new[] { "Administrator" }, Array.Empty()),
new ListSecuredWritesCommand(null, null), Guid.NewGuid().ToString("N"));
/// The list sweep purges terminal rows older than the retention window.
[Fact]
public void List_PurgesTerminalRowsOlderThanRetention()
{
UseOptions(new ManagementServiceOptions
{
SecuredWriteTerminalRetention = TimeSpan.FromDays(30),
SecuredWriteTerminalPurgeInterval = TimeSpan.Zero,
});
DateTime? cutoff = null;
_repo.PurgeTerminalAsync(Arg.Do(c => cutoff = c), Arg.Any(), Arg.Any())
.Returns(3);
CreateActor().Tell(List());
ExpectMsg(TimeSpan.FromSeconds(5));
Assert.NotNull(cutoff);
Assert.InRange(cutoff!.Value, DateTime.UtcNow.AddDays(-30).AddMinutes(-1), DateTime.UtcNow.AddDays(-30).AddMinutes(1));
}
/// A non-positive retention disables the purge.
[Fact]
public void List_WithRetentionDisabled_DoesNotPurge()
{
UseOptions(new ManagementServiceOptions
{
SecuredWriteTerminalRetention = TimeSpan.Zero,
SecuredWriteTerminalPurgeInterval = TimeSpan.Zero,
});
CreateActor().Tell(List());
ExpectMsg(TimeSpan.FromSeconds(5));
_repo.DidNotReceive().PurgeTerminalAsync(Arg.Any(), Arg.Any(), Arg.Any());
}
/// A purge failure is best-effort — the list still answers.
[Fact]
public void List_PurgeFailure_DoesNotFailTheList()
{
UseOptions(new ManagementServiceOptions { SecuredWriteTerminalPurgeInterval = TimeSpan.Zero });
_repo.PurgeTerminalAsync(Arg.Any(), Arg.Any(), Arg.Any())
.ThrowsAsync(new InvalidOperationException("db down"));
CreateActor().Tell(List());
ExpectMsg(TimeSpan.FromSeconds(5));
}
}