fix(deployment): purge SystemArtifactDeploymentRecords in the terminal-record retention sweep (review 2026-10-21 08/G2)
This commit is contained in:
+52
@@ -0,0 +1,52 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Deployment;
|
||||
using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Repositories;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Retention purge for <see cref="SystemArtifactDeploymentRecord"/> rows (review 2026-10-21
|
||||
/// 08/G2): one row is written per Deploy Artifacts run and nothing reads them back, so they
|
||||
/// must share the terminal deployment-record retention window.
|
||||
/// </summary>
|
||||
public sealed class SystemArtifactDeploymentPurgeTests : IDisposable
|
||||
{
|
||||
private readonly ScadaBridgeDbContext _context = SqliteTestHelper.CreateInMemoryContext();
|
||||
private readonly DeploymentManagerRepository _repository;
|
||||
|
||||
/// <summary>Initializes the repository over a fresh in-memory SQLite context.</summary>
|
||||
public SystemArtifactDeploymentPurgeTests()
|
||||
{
|
||||
_repository = new DeploymentManagerRepository(_context);
|
||||
}
|
||||
|
||||
/// <summary>Releases the in-memory database.</summary>
|
||||
public void Dispose() => _context.Dispose();
|
||||
|
||||
private async Task SeedAsync(DateTimeOffset deployedAt)
|
||||
{
|
||||
await _repository.AddSystemArtifactDeploymentAsync(
|
||||
new SystemArtifactDeploymentRecord("Artifacts", "admin") { DeployedAt = deployedAt });
|
||||
await _repository.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>Only rows older than the cutoff are deleted, in bounded batches.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Purge_DeletesOnlyRowsOlderThanCutoff_InBoundedBatches()
|
||||
{
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
await SeedAsync(now.AddDays(-400));
|
||||
await SeedAsync(now.AddDays(-380));
|
||||
await SeedAsync(now.AddDays(-370));
|
||||
await SeedAsync(now.AddDays(-1));
|
||||
var cutoff = now.AddDays(-365);
|
||||
|
||||
Assert.Equal(2, await _repository.PurgeSystemArtifactDeploymentRecordsAsync(cutoff, batchSize: 2));
|
||||
Assert.Equal(1, await _repository.PurgeSystemArtifactDeploymentRecordsAsync(cutoff, batchSize: 2));
|
||||
Assert.Equal(0, await _repository.PurgeSystemArtifactDeploymentRecordsAsync(cutoff, batchSize: 2));
|
||||
|
||||
var remaining = await _repository.GetAllSystemArtifactDeploymentsAsync();
|
||||
var survivor = Assert.Single(remaining);
|
||||
Assert.True(survivor.DeployedAt > cutoff);
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,32 @@ public class DeploymentServiceTests : TestKit
|
||||
PendingDeploymentTtl = TestPendingTtl
|
||||
});
|
||||
|
||||
// ── Terminal-record retention (review 2026-10-21 08/G2) ──
|
||||
|
||||
/// <summary>
|
||||
/// The retention sweep also purges system artifact deployment records — one per Deploy
|
||||
/// Artifacts run, never read back — with the same cutoff as terminal deployment records.
|
||||
/// NOTE: the sweep is rate-limited by a process-wide static, so this is the only test in
|
||||
/// the assembly that may drive <see cref="DeploymentService.TryPurgeTerminalDeploymentRecordsAsync"/>.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task TryPurgeTerminalDeploymentRecords_AlsoPurgesSystemArtifactRecords()
|
||||
{
|
||||
_repo.PurgeTerminalDeploymentRecordsAsync(Arg.Any<DateTimeOffset>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns(1);
|
||||
_repo.PurgeSystemArtifactDeploymentRecordsAsync(Arg.Any<DateTimeOffset>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns(3);
|
||||
|
||||
var purged = await _service.TryPurgeTerminalDeploymentRecordsAsync();
|
||||
|
||||
Assert.Equal(4, purged);
|
||||
var expectedCutoff = DateTimeOffset.UtcNow - new DeploymentManagerOptions().TerminalDeploymentRecordRetention;
|
||||
await _repo.Received(1).PurgeSystemArtifactDeploymentRecordsAsync(
|
||||
Arg.Is<DateTimeOffset>(c => (c - expectedCutoff).Duration() < TimeSpan.FromMinutes(1)),
|
||||
Arg.Any<int>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
// ── Deployment flow ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async instance not found returns failure.</summary>
|
||||
|
||||
Reference in New Issue
Block a user