fix(deployment): pending-deployment supersede/purge/stage deletes never read ConfigurationJson; drop stale deferred-TODO comment (review 2026-10-21 08/P6, 08/C2)
This commit is contained in:
+73
-1
@@ -18,11 +18,12 @@ public class PendingDeploymentRepositoryTests : IDisposable
|
||||
{
|
||||
private readonly ScadaBridgeDbContext _context;
|
||||
private readonly DeploymentManagerRepository _repository;
|
||||
private readonly SqlReaderCommandRecorder _recorder = new();
|
||||
|
||||
/// <summary>Initializes the test class with an in-memory context and repository.</summary>
|
||||
public PendingDeploymentRepositoryTests()
|
||||
{
|
||||
_context = SqliteTestHelper.CreateInMemoryContext();
|
||||
_context = SqliteTestHelper.CreateInMemoryContext(_recorder);
|
||||
_repository = new DeploymentManagerRepository(_context);
|
||||
}
|
||||
|
||||
@@ -344,4 +345,75 @@ public class PendingDeploymentRepositoryTests : IDisposable
|
||||
Assert.Null(await _repository.GetPendingDeploymentByIdAsync("stale-b"));
|
||||
Assert.NotNull(await _repository.GetPendingDeploymentByIdAsync("dep3"));
|
||||
}
|
||||
|
||||
// ── review 2026-10-21 08/P6: pending deletes never materialise ConfigurationJson ──
|
||||
|
||||
/// <summary>True when the change tracker holds a pending row whose JSON is <paramref name="json"/>.</summary>
|
||||
private bool TracksPendingJson(string json) =>
|
||||
_context.ChangeTracker.Entries<PendingDeployment>().Any(e => e.Entity.ConfigurationJson == json);
|
||||
|
||||
/// <summary>
|
||||
/// Supersession must delete the prior row by key, without loading its (tens-of-KB)
|
||||
/// flattened config into the change tracker.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AddPendingDeployment_Supersede_DoesNotMaterialisePriorConfigJson()
|
||||
{
|
||||
var instanceId = await SeedInstanceAsync("InstP6a");
|
||||
await _repository.AddPendingDeploymentAsync(NewPending("old", instanceId, "{\"prior\":true}"));
|
||||
await _repository.SaveChangesAsync();
|
||||
_context.ChangeTracker.Clear();
|
||||
_recorder.Reset();
|
||||
|
||||
await _repository.AddPendingDeploymentAsync(NewPending("new", instanceId, "{\"v\":2}"));
|
||||
|
||||
Assert.False(TracksPendingJson("{\"prior\":true}"));
|
||||
Assert.False(_recorder.AnySelectReads("ConfigurationJson"));
|
||||
await _repository.SaveChangesAsync();
|
||||
Assert.Null(await _repository.GetPendingDeploymentByIdAsync("old"));
|
||||
Assert.NotNull(await _repository.GetPendingDeploymentByIdAsync("new"));
|
||||
}
|
||||
|
||||
/// <summary>The TTL purge is a set-based delete: nothing is loaded into the tracker.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task PurgeExpiredPendingDeployments_DoesNotMaterialiseRows()
|
||||
{
|
||||
var instanceId = await SeedInstanceAsync("InstP6b");
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
await _repository.AddPendingDeploymentAsync(new PendingDeployment(
|
||||
"exp", instanceId, "rev", "{\"expired\":true}", "tok", now.AddMinutes(-20), now.AddMinutes(-10)));
|
||||
await _repository.SaveChangesAsync();
|
||||
_context.ChangeTracker.Clear();
|
||||
_recorder.Reset();
|
||||
|
||||
var purged = await _repository.PurgeExpiredPendingDeploymentsAsync(now);
|
||||
|
||||
Assert.Equal(1, purged);
|
||||
Assert.False(_recorder.AnySelectReads("ConfigurationJson"));
|
||||
Assert.Null(await _repository.GetPendingDeploymentByIdAsync("exp"));
|
||||
}
|
||||
|
||||
/// <summary>StagePendingIfAbsent drops expired rows without reading their config.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StagePendingIfAbsent_ExpiredCleanup_DoesNotMaterialiseExpiredConfigJson()
|
||||
{
|
||||
var instanceId = await SeedInstanceAsync("InstP6c");
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
await _repository.AddPendingDeploymentAsync(new PendingDeployment(
|
||||
"dep-x", instanceId, "rev", "{\"expired\":true}", "tok", now.AddMinutes(-20), now.AddMinutes(-10)));
|
||||
await _repository.SaveChangesAsync();
|
||||
_context.ChangeTracker.Clear();
|
||||
_recorder.Reset();
|
||||
|
||||
var staged = await _repository.StagePendingIfAbsentAsync(
|
||||
instanceId, "dep-x", "rev2", "{\"fresh\":true}", "tok2", now, now.AddMinutes(5));
|
||||
|
||||
Assert.True(staged);
|
||||
Assert.False(_recorder.AnySelectReads("ConfigurationJson"));
|
||||
var row = await _repository.GetPendingDeploymentByIdAsync("dep-x");
|
||||
Assert.Equal("{\"fresh\":true}", row!.ConfigurationJson);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user