docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
This commit is contained in:
@@ -23,7 +23,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Types.Templates;
|
||||
namespace ZB.MOM.WW.ScadaBridge.DeploymentManager.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// WP-1/2/4/5/6/8/16: Tests for central-side DeploymentService.
|
||||
/// Tests for central-side DeploymentService.
|
||||
/// </summary>
|
||||
public class DeploymentServiceTests : TestKit
|
||||
{
|
||||
@@ -34,6 +34,7 @@ public class DeploymentServiceTests : TestKit
|
||||
private readonly IAuditService _audit;
|
||||
private readonly DeploymentService _service;
|
||||
|
||||
/// <summary>Initializes a new instance of the <c>DeploymentServiceTests</c> class.</summary>
|
||||
public DeploymentServiceTests()
|
||||
{
|
||||
_repo = Substitute.For<IDeploymentManagerRepository>();
|
||||
@@ -62,7 +63,7 @@ public class DeploymentServiceTests : TestKit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Notify-and-fetch (Task 6): the central HTTP base URL carried in the
|
||||
/// Notify-and-fetch: the central HTTP base URL carried in the
|
||||
/// <c>RefreshDeploymentCommand</c>. Must be non-empty for the deploy path to
|
||||
/// clear the fail-fast configuration guard.
|
||||
/// </summary>
|
||||
@@ -86,8 +87,10 @@ public class DeploymentServiceTests : TestKit
|
||||
PendingDeploymentTtl = TestPendingTtl
|
||||
});
|
||||
|
||||
// ── WP-1: Deployment flow ──
|
||||
// ── Deployment flow ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async instance not found returns failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_InstanceNotFound_ReturnsFailure()
|
||||
{
|
||||
@@ -99,6 +102,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async validation fails returns failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_ValidationFails_ReturnsFailure()
|
||||
{
|
||||
@@ -119,6 +124,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("validation failed", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async flattening fails returns failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_FlatteningFails_ReturnsFailure()
|
||||
{
|
||||
@@ -134,12 +141,14 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("Validation failed", result.Error);
|
||||
}
|
||||
|
||||
// ── DeploymentManager-021: missing Site row -> hard failure, no silent fabrication ──
|
||||
// ── Missing Site row -> hard failure, no silent fabrication ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async site row missing fails loudly instead of silently substituting.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_SiteRowMissing_FailsLoudlyInsteadOfSilentlySubstituting()
|
||||
{
|
||||
// DeploymentManager-021 regression: previously ResolveSiteIdentifierAsync
|
||||
// Regression: Previously ResolveSiteIdentifierAsync
|
||||
// silently returned the numeric siteId rendered as a string when the
|
||||
// site row was missing (FK was deleted, race with admin delete, DB
|
||||
// inconsistency). That bogus identifier then surfaced downstream as a
|
||||
@@ -181,12 +190,14 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("not found", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// ── DeploymentManager-022: no transient Pending -> single InProgress insert ──
|
||||
// ── No transient Pending -> single InProgress insert ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async no transient pending write record created directly in progress.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_NoTransientPendingWrite_RecordCreatedDirectlyInProgress()
|
||||
{
|
||||
// DeploymentManager-022 regression: previously the deploy path wrote
|
||||
// Regression: Previously the deploy path wrote
|
||||
// the record as Pending, then immediately updated it to InProgress
|
||||
// with no work in between — an extra SaveChangesAsync round-trip, an
|
||||
// extra notifier push, and a Pending->InProgress flicker in the
|
||||
@@ -222,8 +233,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
// ── WP-2: Deployment identity ──
|
||||
// ── Deployment identity ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async creates unique deployment id.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_CreatesUniqueDeploymentId()
|
||||
{
|
||||
@@ -257,8 +270,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal("sha256:abc", captured.RevisionHash);
|
||||
}
|
||||
|
||||
// ── WP-4: State transition validation ──
|
||||
// ── State transition validation ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async enabled instance allows deploy.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_EnabledInstance_AllowsDeploy()
|
||||
{
|
||||
@@ -277,8 +292,10 @@ public class DeploymentServiceTests : TestKit
|
||||
await _repo.Received().AddDeploymentRecordAsync(Arg.Any<DeploymentRecord>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
// ── DeploymentManager-001: unexpected exception must not leave record InProgress ──
|
||||
// ── Unexpected exception must not leave record InProgress ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async communication throws unexpected exception record marked failed.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_CommunicationThrowsUnexpectedException_RecordMarkedFailed()
|
||||
{
|
||||
@@ -310,8 +327,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.NotNull(captured.CompletedAt);
|
||||
}
|
||||
|
||||
// ── DeploymentManager-002: failure write must not use a cancelled token ──
|
||||
// ── Failure write must not use a cancelled token ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async failure write uses non cancellable token.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_FailureWrite_UsesNonCancellableToken()
|
||||
{
|
||||
@@ -355,6 +374,8 @@ public class DeploymentServiceTests : TestKit
|
||||
|
||||
// ── AskTimeoutException: Akka's AskTimeoutException must be classified as a deploy timeout ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async ask timeout exception classified as timeout.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_AskTimeoutException_ClassifiedAsTimeout()
|
||||
{
|
||||
@@ -363,7 +384,7 @@ public class DeploymentServiceTests : TestKit
|
||||
// AskTimeoutException (which does NOT derive from System.TimeoutException).
|
||||
// Prior to the fix this fell through to the generic "Deployment error:"
|
||||
// branch instead of the timeout "Communication failure:" branch, which
|
||||
// broke the DeploymentManager-006 reconciliation that keys off that prefix.
|
||||
// broke the query-the-site-before-redeploy reconciliation that keys off that prefix.
|
||||
var instance = new Instance("AskTimeoutInst") { Id = 99, SiteId = 1, State = InstanceState.NotDeployed };
|
||||
_repo.GetInstanceByIdAsync(99, Arg.Any<CancellationToken>()).Returns(instance);
|
||||
SetupValidPipeline(99, "AskTimeoutInst", "sha256:target");
|
||||
@@ -403,7 +424,7 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.DoesNotContain("Deployment failed:", result.Error);
|
||||
|
||||
// The deployment record must be Failed with the timeout-failure prefix
|
||||
// (which the DeploymentManager-006 reconciliation check keys off).
|
||||
// (which the query-the-site-before-redeploy reconciliation check keys off).
|
||||
Assert.NotNull(captured);
|
||||
Assert.Equal(DeploymentStatus.Failed, captured!.Status);
|
||||
Assert.NotNull(captured.ErrorMessage);
|
||||
@@ -411,8 +432,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.DoesNotContain("Deployment error:", captured.ErrorMessage);
|
||||
}
|
||||
|
||||
// ── WP-6: Lifecycle commands ──
|
||||
// ── Lifecycle commands ──
|
||||
|
||||
/// <summary>Verifies that disable instance async instance not found returns failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DisableInstanceAsync_InstanceNotFound_ReturnsFailure()
|
||||
{
|
||||
@@ -424,6 +447,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that disable instance async when disabled returns transition error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DisableInstanceAsync_WhenDisabled_ReturnsTransitionError()
|
||||
{
|
||||
@@ -436,6 +461,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("not allowed", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that enable instance async when enabled returns transition error.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task EnableInstanceAsync_WhenEnabled_ReturnsTransitionError()
|
||||
{
|
||||
@@ -448,6 +475,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("not allowed", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete instance async instance not found returns failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteInstanceAsync_InstanceNotFound_ReturnsFailure()
|
||||
{
|
||||
@@ -459,10 +488,12 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("not found", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete instance async not deployed removes record without site round trip.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteInstanceAsync_NotDeployed_RemovesRecordWithoutSiteRoundTrip()
|
||||
{
|
||||
// #05-T18: a NotDeployed instance has no live actor at any site (Transport-
|
||||
// A NotDeployed instance has no live actor at any site (Transport-
|
||||
// imported instances always land NotDeployed, often against an uncommissioned
|
||||
// or unreachable site). Delete must succeed as a central-side record cleanup
|
||||
// WITHOUT contacting the site. The default _service's CommunicationService has
|
||||
@@ -478,8 +509,10 @@ public class DeploymentServiceTests : TestKit
|
||||
await _repo.Received(1).DeleteInstanceAsync(70, Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
// ── DeploymentManager-004: site-success but central-delete-failure must not escape uncaught ──
|
||||
// ── Site-success but central-delete-failure must not escape uncaught ──
|
||||
|
||||
/// <summary>Verifies that delete instance async site succeeds central delete fails returns distinct failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteInstanceAsync_SiteSucceeds_CentralDeleteFails_ReturnsDistinctFailure()
|
||||
{
|
||||
@@ -507,8 +540,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("central", result.Error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// ── WP-8: Deployment comparison ──
|
||||
// ── Deployment comparison ──
|
||||
|
||||
/// <summary>Verifies that get deployment comparison async no snapshot returns failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetDeploymentComparisonAsync_NoSnapshot_ReturnsFailure()
|
||||
{
|
||||
@@ -520,6 +555,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Contains("No deployed snapshot", result.Error);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that get deployment comparison async same hash not stale.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetDeploymentComparisonAsync_SameHash_NotStale()
|
||||
{
|
||||
@@ -554,6 +591,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.False(result.Value.IsStale);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that get deployment comparison async different hash is stale.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetDeploymentComparisonAsync_DifferentHash_IsStale()
|
||||
{
|
||||
@@ -591,8 +630,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.True(result.Value.IsStale);
|
||||
}
|
||||
|
||||
// ── PLAN-05 Task 16: read-only paths skip Roslyn script compilation ──
|
||||
// ── Read-only paths skip Roslyn script compilation ──
|
||||
|
||||
/// <summary>Verifies that get deployment comparison async requests validate scripts false.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetDeploymentComparisonAsync_RequestsValidateScriptsFalse()
|
||||
{
|
||||
@@ -622,6 +663,8 @@ public class DeploymentServiceTests : TestKit
|
||||
await _pipeline.DidNotReceive().FlattenAndValidateAsync(1, Arg.Any<CancellationToken>(), true);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async requests validate scripts true.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_RequestsValidateScriptsTrue()
|
||||
{
|
||||
@@ -642,6 +685,8 @@ public class DeploymentServiceTests : TestKit
|
||||
await _pipeline.Received().FlattenAndValidateAsync(1, Arg.Any<CancellationToken>(), true);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that stale instance probe requests validate scripts false.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StaleInstanceProbe_RequestsValidateScriptsFalse()
|
||||
{
|
||||
@@ -663,8 +708,10 @@ public class DeploymentServiceTests : TestKit
|
||||
await _pipeline.Received(1).FlattenAndValidateAsync(5, Arg.Any<CancellationToken>(), false, Arg.Any<FlattenSession?>());
|
||||
}
|
||||
|
||||
// ── DeploymentManager-007: comparison must produce a structured diff ──
|
||||
// ── Comparison must produce a structured diff ──
|
||||
|
||||
/// <summary>Verifies that get deployment comparison async produces structured diff.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetDeploymentComparisonAsync_ProducesStructuredDiff()
|
||||
{
|
||||
@@ -710,6 +757,8 @@ public class DeploymentServiceTests : TestKit
|
||||
|
||||
// ── I-1 (latent): old-form List snapshot must not show a spurious stale/diff ──
|
||||
|
||||
/// <summary>Verifies that get deployment comparison async old form list value normalized to native not stale no diff.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetDeploymentComparisonAsync_OldFormListValue_NormalizedToNative_NotStaleNoDiff()
|
||||
{
|
||||
@@ -785,6 +834,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(nativeHash, result.Value.DeployedRevisionHash);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that get deployment comparison async genuinely changed list value still stale and diffs.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetDeploymentComparisonAsync_GenuinelyChangedListValue_StillStaleAndDiffs()
|
||||
{
|
||||
@@ -845,8 +896,10 @@ public class DeploymentServiceTests : TestKit
|
||||
c => c.CanonicalName == "Thresholds" && c.ChangeType == DiffChangeType.Changed);
|
||||
}
|
||||
|
||||
// ── WP-2: GetDeploymentStatusAsync ──
|
||||
// ── GetDeploymentStatusAsync ──
|
||||
|
||||
/// <summary>Verifies that get deployment status async returns record by deployment id.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetDeploymentStatusAsync_ReturnsRecordByDeploymentId()
|
||||
{
|
||||
@@ -865,10 +918,12 @@ public class DeploymentServiceTests : TestKit
|
||||
|
||||
// ── Audit logging ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async flattening fails does not reach audit.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_FlatteningFails_DoesNotReachAudit()
|
||||
{
|
||||
// DeploymentManager-011: this test previously asserted nothing. A
|
||||
// This test previously asserted nothing. A
|
||||
// flatten failure returns before any site communication, so no audit
|
||||
// entry is written.
|
||||
var instance = new Instance("TestInst") { Id = 1, SiteId = 1, State = InstanceState.NotDeployed };
|
||||
@@ -884,10 +939,12 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<string>(), Arg.Any<object>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async site succeeds writes deploy audit entry.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_SiteSucceeds_WritesDeployAuditEntry()
|
||||
{
|
||||
// DeploymentManager-011: a successful deployment must write a "Deploy"
|
||||
// A successful deployment must write a "Deploy"
|
||||
// audit entry referencing the deployed instance.
|
||||
var instance = new Instance("AuditInst") { Id = 50, SiteId = 1, State = InstanceState.NotDeployed };
|
||||
_repo.GetInstanceByIdAsync(50, Arg.Any<CancellationToken>()).Returns(instance);
|
||||
@@ -908,10 +965,12 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<object>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async deploy audit write throws keeps committed success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_DeployAuditWriteThrows_KeepsCommittedSuccess()
|
||||
{
|
||||
// #05-T17: the post-success "Deploy" audit write is best-effort. If it
|
||||
// The post-success "Deploy" audit write is best-effort. If it
|
||||
// throws, the already-committed Success record must NOT be flipped to
|
||||
// Failed by the outer catch — "audit-write failure NEVER aborts the
|
||||
// user-facing action".
|
||||
@@ -940,6 +999,8 @@ public class DeploymentServiceTests : TestKit
|
||||
|
||||
// ── Telemetry follow-on: scadabridge.deployments.applied on deploy success ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async site succeeds emits deployments applied counter once.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_SiteSucceeds_EmitsDeploymentsAppliedCounterOnce()
|
||||
{
|
||||
@@ -983,6 +1044,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(1, Interlocked.Read(ref applied));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async reconciled does not emit deployments applied counter.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_Reconciled_DoesNotEmitDeploymentsAppliedCounter()
|
||||
{
|
||||
@@ -1038,8 +1101,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(0, Interlocked.Read(ref applied));
|
||||
}
|
||||
|
||||
// ── DeploymentManager-011: lifecycle success paths ──
|
||||
// ── Lifecycle success paths ──
|
||||
|
||||
/// <summary>Verifies that disable instance async site succeeds sets disabled state and audits.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DisableInstanceAsync_SiteSucceeds_SetsDisabledStateAndAudits()
|
||||
{
|
||||
@@ -1061,6 +1126,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<object>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that enable instance async site succeeds sets enabled state and audits.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task EnableInstanceAsync_SiteSucceeds_SetsEnabledStateAndAudits()
|
||||
{
|
||||
@@ -1082,6 +1149,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<object>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that delete instance async site succeeds removes record and audits.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteInstanceAsync_SiteSucceeds_RemovesRecordAndAudits()
|
||||
{
|
||||
@@ -1102,10 +1171,12 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<object>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async same instance operation lock serializes concurrent deploys.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_SameInstance_OperationLockSerializesConcurrentDeploys()
|
||||
{
|
||||
// DeploymentManager-011: two concurrent deploys of the SAME instance
|
||||
// Two concurrent deploys of the SAME instance
|
||||
// must be serialized by the per-instance operation lock — the site sees
|
||||
// them one at a time, never overlapping.
|
||||
var instance = new Instance("LockInst") { Id = 54, SiteId = 1, State = InstanceState.Enabled };
|
||||
@@ -1130,7 +1201,7 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(1, serializationCounters.MaxConcurrent);
|
||||
}
|
||||
|
||||
// ── DeploymentManager-006: query-the-site-before-redeploy idempotency ──
|
||||
// ── Query-the-site-before-redeploy idempotency ──
|
||||
|
||||
/// <summary>
|
||||
/// Builds a DeploymentService whose CommunicationService is backed by the
|
||||
@@ -1172,11 +1243,11 @@ public class DeploymentServiceTests : TestKit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeploymentManager-021 test helper: returns an <see cref="ISiteRepository"/>
|
||||
/// Test helper: Returns an <see cref="ISiteRepository"/>
|
||||
/// substitute that resolves <see cref="ISiteRepository.GetSiteByIdAsync"/>
|
||||
/// for ANY integer id to a stub <see cref="Site"/> whose
|
||||
/// <c>SiteIdentifier</c> is <c>"site-{id}"</c>. Prior to the
|
||||
/// DeploymentManager-021 fix the production `ResolveSiteIdentifierAsync`
|
||||
/// missing-site-row fix the production `ResolveSiteIdentifierAsync`
|
||||
/// silently substituted the numeric id when the site row was missing, so
|
||||
/// these tests passed without seeding any Sites. After the fix a missing
|
||||
/// site throws — every test that drives a deploy/lifecycle path needs a
|
||||
@@ -1195,6 +1266,8 @@ public class DeploymentServiceTests : TestKit
|
||||
return siteRepo;
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async prior in progress record site has target hash marks success without redeploy.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_PriorInProgressRecord_SiteHasTargetHash_MarksSuccessWithoutRedeploy()
|
||||
{
|
||||
@@ -1230,6 +1303,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<DeploymentRecord>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async prior in progress record site has different hash proceeds with deploy.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_PriorInProgressRecord_SiteHasDifferentHash_ProceedsWithDeploy()
|
||||
{
|
||||
@@ -1262,6 +1337,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<DeploymentRecord>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async prior failed timeout record queries site.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_PriorFailedTimeoutRecord_QueriesSite()
|
||||
{
|
||||
@@ -1292,6 +1369,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(DeploymentStatus.Success, prior.Status);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async prior success record skips site query.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_PriorSuccessRecord_SkipsSiteQuery()
|
||||
{
|
||||
@@ -1321,6 +1400,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(1, counters.DeployCount);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async fresh first time deploy skips site query.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_FreshFirstTimeDeploy_SkipsSiteQuery()
|
||||
{
|
||||
@@ -1343,6 +1424,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(1, counters.DeployCount);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async prior in progress record query fails falls through to deploy.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_PriorInProgressRecord_QueryFails_FallsThroughToDeploy()
|
||||
{
|
||||
@@ -1375,8 +1458,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(1, counters.DeployCount);
|
||||
}
|
||||
|
||||
// ── Task 6: notify-and-fetch send path (stage PendingDeployment + send RefreshDeploymentCommand) ──
|
||||
// ── Notify-and-fetch send path (stage PendingDeployment + send RefreshDeploymentCommand) ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async success stages pending config then sends refresh notify.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_Success_StagesPendingConfigThenSendsRefreshNotify()
|
||||
{
|
||||
@@ -1433,6 +1518,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<PendingDeployment>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async success does not delete pending row.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_Success_DoesNotDeletePendingRow()
|
||||
{
|
||||
@@ -1461,6 +1548,8 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<string>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deploy instance async central fetch base url empty fails fast without notify or staging.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_CentralFetchBaseUrlEmpty_FailsFastWithoutNotifyOrStaging()
|
||||
{
|
||||
@@ -1496,8 +1585,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<DeploymentRecord>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
// ── DeploymentManager-015: reconciliation must perform the normal success side effects ──
|
||||
// ── Reconciliation must perform the normal success side effects ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async reconciled sets instance enabled and stores snapshot.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_Reconciled_SetsInstanceEnabledAndStoresSnapshot()
|
||||
{
|
||||
@@ -1539,24 +1630,26 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(1, counters.QueryCount);
|
||||
Assert.Equal(0, counters.DeployCount);
|
||||
|
||||
// DeploymentManager-015: the instance State must reflect the deployed
|
||||
// The instance State must reflect the deployed
|
||||
// config the site is actually running.
|
||||
Assert.Equal(InstanceState.Enabled, instance.State);
|
||||
await _repo.Received().UpdateInstanceAsync(instance, Arg.Any<CancellationToken>());
|
||||
|
||||
// DeploymentManager-015: a deployed-config snapshot must be stored so
|
||||
// A deployed-config snapshot must be stored so
|
||||
// GetDeploymentComparisonAsync has something to compare against.
|
||||
Assert.NotNull(storedSnapshot);
|
||||
Assert.Equal(70, storedSnapshot!.InstanceId);
|
||||
Assert.Equal("sha256:target", storedSnapshot.RevisionHash);
|
||||
}
|
||||
|
||||
// ── DeploymentManager-018: reconciliation must preserve an intentional Disabled state ──
|
||||
// ── Reconciliation must preserve an intentional Disabled state ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async reconciled disabled instance preserves disabled state.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_Reconciled_DisabledInstance_PreservesDisabledState()
|
||||
{
|
||||
// DeploymentManager-018: after a central failover, the in-memory
|
||||
// After a central failover, the in-memory
|
||||
// OperationLockManager is lost (by design — in-progress treated as
|
||||
// failed). The prior deployment record remains InProgress in the DB.
|
||||
// The operator can legitimately invoke Disable on the instance between
|
||||
@@ -1596,19 +1689,20 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(1, counters.QueryCount);
|
||||
Assert.Equal(0, counters.DeployCount);
|
||||
|
||||
// DeploymentManager-018: the operator's explicit Disable must survive
|
||||
// The operator's explicit Disable must survive
|
||||
// the reconciliation — Instance.State stays Disabled, not silently
|
||||
// flipped to Enabled.
|
||||
Assert.Equal(InstanceState.Disabled, instance.State);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The counterpart to the bulk path's Disabled skip (arch-review P7-2): a
|
||||
/// The counterpart to the bulk path's Disabled skip: a
|
||||
/// SINGLE-instance deploy of a Disabled instance still deploys AND enables it.
|
||||
/// That is the documented "Deploy on a Disabled instance also enables it"
|
||||
/// transition and the explicit operator action bulk deploy now refuses to
|
||||
/// perform implicitly, so it must keep working exactly as before.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_DisabledInstance_StillDeploysAndEnables()
|
||||
{
|
||||
@@ -1632,8 +1726,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Assert.Equal(InstanceState.Enabled, instance.State);
|
||||
}
|
||||
|
||||
// ── DeploymentManager-016: reconciled record must carry the target revision hash ──
|
||||
// ── Reconciled record must carry the target revision hash ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async reconciled prior record revision hash updated to target.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_Reconciled_PriorRecordRevisionHashUpdatedToTarget()
|
||||
{
|
||||
@@ -1667,17 +1763,19 @@ public class DeploymentServiceTests : TestKit
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
Assert.Equal(DeploymentStatus.Success, prior.Status);
|
||||
// DeploymentManager-016: the persisted record's RevisionHash must agree
|
||||
// The persisted record's RevisionHash must agree
|
||||
// with the site's applied revision, not keep the stale R1.
|
||||
Assert.Equal("sha256:target", prior.RevisionHash);
|
||||
}
|
||||
|
||||
// ── DeploymentManager-020: reconciliation audit attributes to the CURRENT user, not the prior deployer ──
|
||||
// ── Reconciliation audit attributes to the CURRENT user, not the prior deployer ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async reconciled audit attributes current user not prior deployer.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_Reconciled_AuditAttributesCurrentUserNotPriorDeployer()
|
||||
{
|
||||
// DeploymentManager-020: a redeploy that reconciles a timed-out prior
|
||||
// A redeploy that reconciles a timed-out prior
|
||||
// record must be audited as the action of the user driving THIS
|
||||
// redeploy — not the user who originally issued the now-reconciled
|
||||
// deployment. The prior deployer is preserved in the detail object so
|
||||
@@ -1707,7 +1805,7 @@ public class DeploymentServiceTests : TestKit
|
||||
var result = await service.DeployInstanceAsync(73, "currentUser");
|
||||
|
||||
Assert.True(result.IsSuccess);
|
||||
// DeploymentManager-020: audit row's actor is the current user.
|
||||
// Audit row's actor is the current user.
|
||||
await _audit.Received().LogAsync(
|
||||
"currentUser", "DeployReconciled", "Instance", "73", "ReconcileAuditUser",
|
||||
Arg.Any<object>(), Arg.Any<CancellationToken>());
|
||||
@@ -1717,8 +1815,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<object>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
// ── DeploymentManager-012: LifecycleCommandTimeout must actually bound lifecycle commands ──
|
||||
// ── LifecycleCommandTimeout must actually bound lifecycle commands ──
|
||||
|
||||
/// <summary>Verifies that disable instance async site unresponsive lifecycle command timeout bounds the wait.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DisableInstanceAsync_SiteUnresponsive_LifecycleCommandTimeoutBoundsTheWait()
|
||||
{
|
||||
@@ -1771,17 +1871,19 @@ public class DeploymentServiceTests : TestKit
|
||||
/// <summary>Stand-in actor that never replies to anything.</summary>
|
||||
private class SilentProbeActor : ReceiveActor
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <c>SilentProbeActor</c> class.</summary>
|
||||
public SilentProbeActor() => ReceiveAny(_ => { });
|
||||
}
|
||||
|
||||
// ── DeploymentManager-019: lifecycle timeouts must write an audit entry ──
|
||||
// ── Lifecycle timeouts must write an audit entry ──
|
||||
|
||||
/// <summary>
|
||||
/// DeploymentManager-019: when a Disable times out at the site, the
|
||||
/// When a Disable times out at the site, the
|
||||
/// operator's attempted action must still be recorded in the audit log
|
||||
/// with the documented <c>DisableTimedOut</c> action — pre-fix nothing
|
||||
/// was written and the audit trail was silent about the attempt.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DisableInstanceAsync_LifecycleTimeout_WritesDisableTimedOutAuditEntry()
|
||||
{
|
||||
@@ -1833,8 +1935,9 @@ public class DeploymentServiceTests : TestKit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeploymentManager-019: same audit guarantee for the Enable path.
|
||||
/// Same audit guarantee for the Enable path.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task EnableInstanceAsync_LifecycleTimeout_WritesEnableTimedOutAuditEntry()
|
||||
{
|
||||
@@ -1881,8 +1984,9 @@ public class DeploymentServiceTests : TestKit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DeploymentManager-019: same audit guarantee for the Delete path.
|
||||
/// Same audit guarantee for the Delete path.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeleteInstanceAsync_LifecycleTimeout_WritesDeleteTimedOutAuditEntry()
|
||||
{
|
||||
@@ -1928,8 +2032,10 @@ public class DeploymentServiceTests : TestKit
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
// ── DeploymentManager-003: post-success persistence must commit the Success status ──
|
||||
// ── Post-success persistence must commit the Success status ──
|
||||
|
||||
/// <summary>Verifies that deploy instance async site succeeds snapshot write fails record still committed success.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployInstanceAsync_SiteSucceeds_SnapshotWriteFails_RecordStillCommittedSuccess()
|
||||
{
|
||||
@@ -1974,8 +2080,8 @@ public class DeploymentServiceTests : TestKit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-test counters for <see cref="SerializationProbeActor"/>. DeploymentManager-024:
|
||||
/// each test owns its own instance, passed into the actor's constructor, so
|
||||
/// Per-test counters for <see cref="SerializationProbeActor"/>.
|
||||
/// Each test owns its own instance, passed into the actor's constructor, so
|
||||
/// counters are no longer shared static state that races under parallel
|
||||
/// test execution.
|
||||
/// </summary>
|
||||
@@ -1992,13 +2098,18 @@ public class DeploymentServiceTests : TestKit
|
||||
/// same instance were NOT serialized by the operation lock their windows
|
||||
/// would overlap and <c>MaxConcurrent</c> would exceed 1.
|
||||
///
|
||||
/// Notify-and-fetch (Task 6): the deploy path now sends a
|
||||
/// Notify-and-fetch: the deploy path now sends a
|
||||
/// <see cref="RefreshDeploymentCommand"/> instead of <c>DeployInstanceCommand</c>.
|
||||
/// </summary>
|
||||
private class SerializationProbeActor : ReceiveActor, IWithTimers
|
||||
{
|
||||
/// <summary>Gets or sets the actor timer scheduler supplied by Akka.</summary>
|
||||
public ITimerScheduler Timers { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <c>SerializationProbeActor</c> class.
|
||||
/// </summary>
|
||||
/// <param name="counters">The counters.</param>
|
||||
public SerializationProbeActor(SerializationProbeCounters counters)
|
||||
{
|
||||
Receive<SiteEnvelope>(env =>
|
||||
@@ -2043,8 +2154,8 @@ public class DeploymentServiceTests : TestKit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-test counters for <see cref="ReconcileProbeActor"/>. DeploymentManager-024:
|
||||
/// each test owns its own instance so counter assertions cannot race across
|
||||
/// Per-test counters for <see cref="ReconcileProbeActor"/>.
|
||||
/// Each test owns its own instance so counter assertions cannot race across
|
||||
/// tests running in parallel.
|
||||
/// </summary>
|
||||
private sealed class ReconcileProbeCounters
|
||||
@@ -2053,7 +2164,7 @@ public class DeploymentServiceTests : TestKit
|
||||
public int DeployCount;
|
||||
|
||||
/// <summary>
|
||||
/// Notify-and-fetch (Task 6): the most recent
|
||||
/// Notify-and-fetch: the most recent
|
||||
/// <see cref="RefreshDeploymentCommand"/> the probe received, so a test
|
||||
/// can assert the deploy path notified the site with the staged token,
|
||||
/// fetch base URL, and deployment id.
|
||||
@@ -2068,13 +2179,19 @@ public class DeploymentServiceTests : TestKit
|
||||
/// configurable applied revision hash, and (optionally) drops the query to
|
||||
/// simulate an unreachable site so the central Ask times out.
|
||||
///
|
||||
/// Notify-and-fetch (Task 6): the deploy path now sends a
|
||||
/// Notify-and-fetch: the deploy path now sends a
|
||||
/// <see cref="RefreshDeploymentCommand"/> (not the fat
|
||||
/// <c>DeployInstanceCommand</c>), so the probe counts that message as a
|
||||
/// "deploy" and records it for assertion.
|
||||
/// </summary>
|
||||
private class ReconcileProbeActor : ReceiveActor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <c>ReconcileProbeActor</c> class.
|
||||
/// </summary>
|
||||
/// <param name="counters">The counters.</param>
|
||||
/// <param name="siteHash">The site hash.</param>
|
||||
/// <param name="failQuery">The fail query.</param>
|
||||
public ReconcileProbeActor(ReconcileProbeCounters counters, string siteHash, bool failQuery)
|
||||
{
|
||||
Receive<SiteEnvelope>(env =>
|
||||
|
||||
Reference in New Issue
Block a user