test: migrate AdminUI/ControlPlane/Runtime/Host tests to xunit.v3 (#756)
v2-ci / unit-tests (push) Failing after 13m22s
v2-ci / unit-tests (push) Failing after 13m22s
Akka.TestKit.Xunit2 -> Akka.TestKit.Xunit 1.5.62 (xunit.v3 TestKit; needs xunit.v3 3.2.2, set via VersionOverride on the four projects). Removes the xunit 2.9.3 pin. IAsyncLifetime returns ValueTask, SqlServerFactAttribute gains the source-info ctor, and xUnit1051 call sites pass TestContext.Current.CancellationToken. Test counts unchanged. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
<PackageVersion Include="Akka.Remote.Hosting" Version="1.5.62" />
|
||||
<PackageVersion Include="Akka.Streams" Version="1.5.62" />
|
||||
<PackageVersion Include="Akka.Streams.TestKit" Version="1.5.62" />
|
||||
<PackageVersion Include="Akka.TestKit.Xunit2" Version="1.5.62" />
|
||||
<PackageVersion Include="Akka.TestKit.Xunit" Version="1.5.62" />
|
||||
<PackageVersion Include="Avalonia" Version="11.2.7" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.2.7" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.2.7" />
|
||||
@@ -212,13 +212,10 @@
|
||||
Avalonia.X11 (Linux desktop). Pin to the patched 0.21.3 in the same 0.x line.
|
||||
Applied via a direct PackageReference in the consuming desktop project(s). -->
|
||||
<PackageVersion Include="Tmds.DBus.Protocol" Version="0.21.3" />
|
||||
<!-- xunit v2 pin: AdminUI.Tests / ControlPlane.Tests / Runtime.Tests are held on v2 by
|
||||
Akka.TestKit.Xunit2 (no xunit.v3 TestKit ships as of Akka 1.5.62 — both candidate ids
|
||||
Akka.TestKit.Xunit3 / Akka.TestKit.Xunit.v3 return BlobNotFound on nuget.org, verified
|
||||
2026-07-13). All 44 other test projects are on xunit.v3 1.1.0. Remove this pin and migrate
|
||||
the 3 holdouts when Akka ships an xunit.v3 TestKit. (R2-12 / 07/C-4.) -->
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
|
||||
<!-- Akka.TestKit.Xunit 1.5.62 (the xunit.v3 TestKit) requires xunit.v3.assert / extensibility.core >= 3.2.2, so
|
||||
the 4 Akka TestKit test projects (AdminUI/ControlPlane/Runtime/Host.Tests) carry VersionOverride="3.2.2".
|
||||
Move this pin to 3.2.2 repo-wide (and drop the overrides) once the other projects' analyzer fallout is handled. -->
|
||||
<PackageVersion Include="xunit.v3" Version="1.1.0" />
|
||||
<PackageVersion Include="ZB.MOM.WW.Health" Version="0.3.0" />
|
||||
<!--
|
||||
|
||||
@@ -28,28 +28,28 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var roots = await svc.LoadRootsAsync(AclScopeTestDb.ClusterId);
|
||||
var roots = await svc.LoadRootsAsync(AclScopeTestDb.ClusterId, TestContext.Current.CancellationToken);
|
||||
|
||||
var cluster = roots.Raw.ShouldHaveSingleItem();
|
||||
cluster.ScopeKind.ShouldBe(NodeAclScopeKind.Cluster);
|
||||
|
||||
var folder = (await svc.LoadChildrenAsync(cluster)).ShouldHaveSingleItem();
|
||||
var folder = (await svc.LoadChildrenAsync(cluster, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
folder.ScopeKind.ShouldBe(NodeAclScopeKind.RawFolder);
|
||||
folder.ScopeId.ShouldBe(AclScopeTestDb.FolderId);
|
||||
|
||||
var driver = (await svc.LoadChildrenAsync(folder)).ShouldHaveSingleItem();
|
||||
var driver = (await svc.LoadChildrenAsync(folder, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
driver.ScopeKind.ShouldBe(NodeAclScopeKind.RawDriver);
|
||||
driver.ScopeId.ShouldBe(AclScopeTestDb.DriverId);
|
||||
|
||||
var device = (await svc.LoadChildrenAsync(driver)).ShouldHaveSingleItem();
|
||||
var device = (await svc.LoadChildrenAsync(driver, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
device.ScopeKind.ShouldBe(NodeAclScopeKind.RawDevice);
|
||||
device.ScopeId.ShouldBe(AclScopeTestDb.DeviceId);
|
||||
|
||||
var group = (await svc.LoadChildrenAsync(device)).ShouldHaveSingleItem();
|
||||
var group = (await svc.LoadChildrenAsync(device, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
group.ScopeKind.ShouldBe(NodeAclScopeKind.RawTagGroup);
|
||||
group.ScopeId.ShouldBe(AclScopeTestDb.TagGroupId);
|
||||
|
||||
var tag = (await svc.LoadChildrenAsync(group)).ShouldHaveSingleItem();
|
||||
var tag = (await svc.LoadChildrenAsync(group, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
tag.ScopeKind.ShouldBe(NodeAclScopeKind.Tag);
|
||||
tag.ScopeId.ShouldBe(AclScopeTestDb.TagId);
|
||||
}
|
||||
@@ -64,10 +64,10 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var cluster = (await svc.LoadRootsAsync(AclScopeTestDb.ClusterId)).Raw.ShouldHaveSingleItem();
|
||||
var cluster = (await svc.LoadRootsAsync(AclScopeTestDb.ClusterId, TestContext.Current.CancellationToken)).Raw.ShouldHaveSingleItem();
|
||||
|
||||
cluster.ScopeId.ShouldBeNull();
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, cluster.ScopeKind!.Value, cluster.ScopeId))
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, cluster.ScopeKind!.Value, cluster.ScopeId, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
@@ -81,13 +81,13 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var roots = await svc.LoadRootsAsync(AclScopeTestDb.ClusterId);
|
||||
var roots = await svc.LoadRootsAsync(AclScopeTestDb.ClusterId, TestContext.Current.CancellationToken);
|
||||
var reachable = await FlattenAsync(svc, roots);
|
||||
|
||||
reachable.ShouldNotContain(AclScopeTestDb.OtherDriverId);
|
||||
|
||||
// …and it is a real driver, so the absence is the rooting rather than the id being unknown.
|
||||
var side = await svc.LoadRootsAsync(AclScopeTestDb.OtherClusterId);
|
||||
var side = await svc.LoadRootsAsync(AclScopeTestDb.OtherClusterId, TestContext.Current.CancellationToken);
|
||||
(await FlattenAsync(svc, side)).ShouldContain(AclScopeTestDb.OtherDriverId);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var area = (await svc.LoadRootsAsync(AclScopeTestDb.ClusterId)).Uns.ShouldHaveSingleItem();
|
||||
var area = (await svc.LoadRootsAsync(AclScopeTestDb.ClusterId, TestContext.Current.CancellationToken)).Uns.ShouldHaveSingleItem();
|
||||
area.ScopeKind.ShouldBe(NodeAclScopeKind.UnsArea);
|
||||
area.ScopeId.ShouldBe(AclScopeTestDb.AreaId);
|
||||
|
||||
@@ -120,7 +120,7 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var roots = await svc.LoadRootsAsync("NO-SUCH-CLUSTER");
|
||||
var roots = await svc.LoadRootsAsync("NO-SUCH-CLUSTER", TestContext.Current.CancellationToken);
|
||||
|
||||
roots.Raw.ShouldBeEmpty();
|
||||
roots.Uns.ShouldBeEmpty();
|
||||
@@ -146,7 +146,7 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, kind, id))
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, kind, id, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeTrue($"{kind} '{id}' names a real entity of that type");
|
||||
|
||||
// The same id under a different kind must not resolve. RawDevice is the neighbour for every raw
|
||||
@@ -156,7 +156,7 @@ public sealed class AclScopeServiceTests
|
||||
? (kind == NodeAclScopeKind.UnsArea ? NodeAclScopeKind.UnsLine : NodeAclScopeKind.UnsArea)
|
||||
: (kind == NodeAclScopeKind.RawDevice ? NodeAclScopeKind.RawDriver : NodeAclScopeKind.RawDevice);
|
||||
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, neighbour, id))
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, neighbour, id, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeFalse($"'{id}' is not a {neighbour}");
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ public sealed class AclScopeServiceTests
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var check = await svc.CheckScopeAsync(
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.RawDevice, "DEV-typo");
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.RawDevice, "DEV-typo", TestContext.Current.CancellationToken);
|
||||
|
||||
check.Ok.ShouldBeFalse();
|
||||
check.Message.ShouldContain("DEV-typo");
|
||||
@@ -184,7 +184,7 @@ public sealed class AclScopeServiceTests
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var check = await svc.CheckScopeAsync(
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.RawDriver, AclScopeTestDb.OtherDriverId);
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.RawDriver, AclScopeTestDb.OtherDriverId, TestContext.Current.CancellationToken);
|
||||
|
||||
check.Ok.ShouldBeFalse();
|
||||
check.Message.ShouldContain(AclScopeTestDb.OtherClusterId);
|
||||
@@ -192,7 +192,7 @@ public sealed class AclScopeServiceTests
|
||||
// Authored under its own cluster, the same row is fine — so the rejection is the boundary, not
|
||||
// the driver.
|
||||
(await svc.CheckScopeAsync(
|
||||
AclScopeTestDb.OtherClusterId, NodeAclScopeKind.RawDriver, AclScopeTestDb.OtherDriverId))
|
||||
AclScopeTestDb.OtherClusterId, NodeAclScopeKind.RawDriver, AclScopeTestDb.OtherDriverId, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
@@ -206,13 +206,13 @@ public sealed class AclScopeServiceTests
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
(await svc.CheckScopeAsync(
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.Equipment, AclScopeTestDb.OrphanEquipmentId))
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.Equipment, AclScopeTestDb.OrphanEquipmentId, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeFalse();
|
||||
|
||||
// The healthy equipment in the same table passes, so the rejection is the broken line and not the
|
||||
// Equipment arm being wired to nothing.
|
||||
(await svc.CheckScopeAsync(
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.Equipment, AclScopeTestDb.EquipmentId))
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.Equipment, AclScopeTestDb.EquipmentId, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
@@ -226,9 +226,9 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.Cluster, AclScopeTestDb.DeviceId))
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.Cluster, AclScopeTestDb.DeviceId, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeFalse();
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.Cluster, null))
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.Cluster, null, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
@@ -238,9 +238,9 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.RawDevice, null))
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.RawDevice, null, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeFalse();
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.RawDevice, " "))
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.RawDevice, " ", TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeFalse();
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ public sealed class AclScopeServiceTests
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var check = await svc.CheckScopeAsync(
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.FolderSegment, AclScopeTestDb.FolderId);
|
||||
AclScopeTestDb.ClusterId, NodeAclScopeKind.FolderSegment, AclScopeTestDb.FolderId, TestContext.Current.CancellationToken);
|
||||
|
||||
check.Ok.ShouldBeFalse();
|
||||
check.Message.ShouldContain("RawFolder");
|
||||
@@ -272,7 +272,7 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.Namespace, scopeId))
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, NodeAclScopeKind.Namespace, scopeId, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBe(expected);
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, (NodeAclScopeKind)999, "anything"))
|
||||
(await svc.CheckScopeAsync(AclScopeTestDb.ClusterId, (NodeAclScopeKind)999, "anything", TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeFalse();
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var usage = await svc.CheckPrincipalAsync(OpcUaPrincipals.Anonymous);
|
||||
var usage = await svc.CheckPrincipalAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken);
|
||||
|
||||
usage.IsAnonymous.ShouldBeTrue();
|
||||
usage.ReachesSomething.ShouldBeTrue();
|
||||
@@ -309,12 +309,12 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService(new FixedClock(AclScopeTestDb.Now));
|
||||
|
||||
var live = await svc.CheckPrincipalAsync(AclScopeTestDb.LivePrincipal);
|
||||
var live = await svc.CheckPrincipalAsync(AclScopeTestDb.LivePrincipal, TestContext.Current.CancellationToken);
|
||||
live.EnabledKeys.ShouldBe(2);
|
||||
live.DisabledKeys.ShouldBe(1);
|
||||
live.ReachesSomething.ShouldBeTrue();
|
||||
|
||||
var stale = await svc.CheckPrincipalAsync(AclScopeTestDb.StalePrincipal);
|
||||
var stale = await svc.CheckPrincipalAsync(AclScopeTestDb.StalePrincipal, TestContext.Current.CancellationToken);
|
||||
stale.EnabledKeys.ShouldBe(0, "its only key expired yesterday");
|
||||
stale.DisabledKeys.ShouldBe(1);
|
||||
stale.ReachesSomething.ShouldBeFalse();
|
||||
@@ -326,7 +326,7 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var usage = await svc.CheckPrincipalAsync("scadabridge-dcI"); // capital i, not lowercase L
|
||||
var usage = await svc.CheckPrincipalAsync("scadabridge-dcI", TestContext.Current.CancellationToken); // capital i, not lowercase L
|
||||
|
||||
usage.EnabledKeys.ShouldBe(0);
|
||||
usage.ReachesSomething.ShouldBeFalse();
|
||||
@@ -338,7 +338,7 @@ public sealed class AclScopeServiceTests
|
||||
{
|
||||
var svc = AclScopeTestDb.NewService();
|
||||
|
||||
var known = await svc.LoadKnownPrincipalsAsync();
|
||||
var known = await svc.LoadKnownPrincipalsAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
known.ShouldContain(AclScopeTestDb.LivePrincipal);
|
||||
known.ShouldContain(AclScopeTestDb.StalePrincipal);
|
||||
|
||||
@@ -25,10 +25,8 @@ namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests;
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The skip is an attribute rather than the <c>Assert.Skip(fixture.SkipReason)</c> call the S7
|
||||
/// integration suite uses, because AdminUI.Tests is one of the three projects pinned to
|
||||
/// <b>xunit v2</b> by <c>Akka.TestKit.Xunit2</c> (see <c>Directory.Packages.props</c>) and
|
||||
/// <c>Assert.Skip</c> is xunit v3 only. Overriding <c>FactAttribute.Skip</c> is the v2 equivalent
|
||||
/// and reports a genuine "skipped", not a vacuous pass. Both the attribute and this fixture read
|
||||
/// integration suite uses, so that the skip is decided before the test runs and
|
||||
/// the <c>FactAttribute.Skip</c> override reports a genuine "skipped", not a vacuous pass. Both the attribute and this fixture read
|
||||
/// the same one-shot probe in <see cref="AdminUiSqlServer"/>, so a session probes once.
|
||||
/// </para>
|
||||
/// <para>
|
||||
@@ -382,14 +380,17 @@ END";
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="FactAttribute"/> that skips itself when the shared test SQL Server is unreachable.
|
||||
/// The xunit v2 equivalent of xunit v3's <c>Assert.Skip</c> — see
|
||||
/// Attribute-level counterpart of xunit v3's <c>Assert.Skip</c> — see
|
||||
/// <see cref="AdminUiSqlServerFixture"/>'s remarks for why this project cannot use the latter.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class SqlServerFactAttribute : FactAttribute
|
||||
{
|
||||
/// <summary>Initializes a new instance of the <see cref="SqlServerFactAttribute"/> class.</summary>
|
||||
public SqlServerFactAttribute()
|
||||
public SqlServerFactAttribute(
|
||||
[System.Runtime.CompilerServices.CallerFilePath] string? sourceFilePath = null,
|
||||
[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = -1)
|
||||
: base(sourceFilePath, sourceLineNumber)
|
||||
{
|
||||
if (AdminUiSqlServer.SkipReason is { } reason) Skip = reason;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Akka.Actor;
|
||||
using Akka.Hosting;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Clients;
|
||||
@@ -59,7 +59,7 @@ public sealed class AlarmsHistorianReaderTests : TestKit
|
||||
reading.Availability.ShouldBe(HistorianSinkAvailability.Unavailable);
|
||||
reading.Message!.ShouldContain("did not answer");
|
||||
// Positive control: the Ask really was delivered — the classification is about the missing reply.
|
||||
silent.ExpectMsg<HistorianAdapterActor.GetStatus>();
|
||||
silent.ExpectMsg<HistorianAdapterActor.GetStatus>(cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Akka.Hosting;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Clients;
|
||||
@@ -52,7 +52,7 @@ public sealed class AlertsAckPathTests : TestKit
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Message.ShouldBeNull();
|
||||
var sent = mesh.ExpectMsg<MeshCommand>(TimeSpan.FromSeconds(5));
|
||||
var sent = mesh.ExpectMsg<MeshCommand>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
sent.Topic.ShouldBe(AlarmCommandsTopic.Name);
|
||||
var command = sent.Message.ShouldBeOfType<AlarmCommand>();
|
||||
command.AlarmId.ShouldBe("alarm-42");
|
||||
@@ -74,7 +74,7 @@ public sealed class AlertsAckPathTests : TestKit
|
||||
"alarm-7", "operator-kim", ShelveKind.Timed, unshelveAtUtc: until, comment: null, CancellationToken.None);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
var command = mesh.ExpectMsg<MeshCommand>(TimeSpan.FromSeconds(5)).Message.ShouldBeOfType<AlarmCommand>();
|
||||
var command = mesh.ExpectMsg<MeshCommand>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Message.ShouldBeOfType<AlarmCommand>();
|
||||
command.Operation.ShouldBe("TimedShelve");
|
||||
command.UnshelveAtUtc.ShouldBe(until);
|
||||
}
|
||||
|
||||
@@ -41,11 +41,11 @@ public sealed class DeploymentStatusEndpointTests
|
||||
seed.Deployments.Add(new Deployment { DeploymentId = Guid.NewGuid(), RevisionHash = "other", CreatedBy = "x" });
|
||||
seed.NodeDeploymentStates.Add(new NodeDeploymentState { NodeId = "node-b", DeploymentId = id, Status = NodeDeploymentStatus.Applying });
|
||||
seed.NodeDeploymentStates.Add(new NodeDeploymentState { NodeId = "node-a", DeploymentId = id, Status = NodeDeploymentStatus.Applied, AppliedAtUtc = DateTime.UtcNow });
|
||||
await seed.SaveChangesAsync();
|
||||
await seed.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
await using var db = NewDb(name);
|
||||
var status = await DeployApiEndpoints.GetDeploymentStatusAsync(db, id);
|
||||
var status = await DeployApiEndpoints.GetDeploymentStatusAsync(db, id, TestContext.Current.CancellationToken);
|
||||
|
||||
status.ShouldNotBeNull();
|
||||
status!.DeploymentId.ShouldBe(id);
|
||||
@@ -59,7 +59,7 @@ public sealed class DeploymentStatusEndpointTests
|
||||
public async Task GetDeploymentStatusAsync_returns_null_for_an_unknown_id()
|
||||
{
|
||||
await using var db = NewDb(Guid.NewGuid().ToString("N"));
|
||||
(await DeployApiEndpoints.GetDeploymentStatusAsync(db, Guid.NewGuid())).ShouldBeNull();
|
||||
(await DeployApiEndpoints.GetDeploymentStatusAsync(db, Guid.NewGuid(), TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+7
-7
@@ -28,7 +28,7 @@ public sealed class ApiKeyGalaxyAckIdentityServiceTests
|
||||
[Fact]
|
||||
public async Task Set_writes_both_secrets_encrypted_under_the_driver_names()
|
||||
{
|
||||
await Service().SetAsync(KeyId, "alice", "CORP", Password, "admin", default);
|
||||
await Service().SetAsync(KeyId, "alice", "CORP", Password, "admin", TestContext.Current.CancellationToken);
|
||||
|
||||
var user = _store.Rows[GalaxyAckIdentitySecrets.UserSecretName(KeyId)];
|
||||
var pw = _store.Rows[GalaxyAckIdentitySecrets.PasswordSecretName(KeyId)];
|
||||
@@ -48,11 +48,11 @@ public sealed class ApiKeyGalaxyAckIdentityServiceTests
|
||||
public async Task List_returns_identity_and_never_decrypts_a_password()
|
||||
{
|
||||
var svc = Service();
|
||||
await svc.SetAsync(KeyId, "alice", "CORP", Password, "admin", default);
|
||||
await svc.SetAsync("ffff0000ffff0000", "bob", null, "", "admin", default);
|
||||
await svc.SetAsync(KeyId, "alice", "CORP", Password, "admin", TestContext.Current.CancellationToken);
|
||||
await svc.SetAsync("ffff0000ffff0000", "bob", null, "", "admin", TestContext.Current.CancellationToken);
|
||||
_store.GetCalls.Clear();
|
||||
|
||||
var list = await svc.ListAsync(default);
|
||||
var list = await svc.ListAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
list[KeyId].ShouldBe("CORP\\alice");
|
||||
list["ffff0000ffff0000"].ShouldBe("bob");
|
||||
@@ -63,12 +63,12 @@ public sealed class ApiKeyGalaxyAckIdentityServiceTests
|
||||
public async Task Clear_removes_both_and_audits_without_the_credential()
|
||||
{
|
||||
var svc = Service();
|
||||
await svc.SetAsync(KeyId, "alice", "CORP", Password, "admin", default);
|
||||
await svc.SetAsync(KeyId, "alice", "CORP", Password, "admin", TestContext.Current.CancellationToken);
|
||||
|
||||
await svc.ClearAsync(KeyId, "admin", default);
|
||||
await svc.ClearAsync(KeyId, "admin", TestContext.Current.CancellationToken);
|
||||
|
||||
_store.Rows.ShouldBeEmpty();
|
||||
(await svc.ListAsync(default)).ShouldBeEmpty();
|
||||
(await svc.ListAsync(TestContext.Current.CancellationToken)).ShouldBeEmpty();
|
||||
_audit.Events.Select(e => e.Action).ShouldBe(["apikey.galaxy-ack-identity.set", "apikey.galaxy-ack-identity.clear"]);
|
||||
_audit.Events.ShouldAllBe(e => e.Target == KeyId && e.Actor == "admin");
|
||||
string.Join("|", _audit.Events.Select(e => e.ToString())).ShouldNotContain(Password);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Akka.Hosting;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.Audit;
|
||||
@@ -36,9 +36,9 @@ public sealed class ActorAuditWriterTests : TestKit
|
||||
DetailsJson = "{}",
|
||||
};
|
||||
|
||||
await writer.WriteAsync(evt);
|
||||
await writer.WriteAsync(evt, TestContext.Current.CancellationToken);
|
||||
|
||||
var received = probe.ExpectMsg<AuditEvent>(TimeSpan.FromSeconds(3));
|
||||
var received = probe.ExpectMsg<AuditEvent>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
received.ShouldBe(evt);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ public sealed class DeploymentDriftProbeTests
|
||||
SourceNode = "node-1",
|
||||
EditedAtUtc = DateTime.UtcNow,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
compose.Result = "a-different-hash";
|
||||
|
||||
@@ -27,16 +27,16 @@ public sealed class FleetDiagnosticsLinksTests
|
||||
DriverInstanceId = "drv-press-1", ClusterId = "MAIN", Name = "Press line Modbus",
|
||||
DriverType = "Modbus", DriverConfig = "{}",
|
||||
});
|
||||
await seed.SaveChangesAsync();
|
||||
await seed.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
await using var db = new OtOpcUaConfigDbContext(options);
|
||||
var names = await FleetDiagnosticsLinks.LoadDisplayNamesAsync(db, ["drv-press-1", "drv-unknown"]);
|
||||
var names = await FleetDiagnosticsLinks.LoadDisplayNamesAsync(db, ["drv-press-1", "drv-unknown"], TestContext.Current.CancellationToken);
|
||||
|
||||
names["DRV-PRESS-1"].ShouldBe("Press line Modbus"); // the returned map is case-insensitive
|
||||
names.ContainsKey("drv-unknown").ShouldBeFalse();
|
||||
|
||||
(await FleetDiagnosticsLinks.LoadDisplayNamesAsync(db, [])).ShouldBeEmpty();
|
||||
(await FleetDiagnosticsLinks.LoadDisplayNamesAsync(db, [], TestContext.Current.CancellationToken)).ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Akka.Hosting;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Grpc.Core;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
@@ -53,7 +53,7 @@ public sealed class CentralCommandGrpcServiceTests : TestKit
|
||||
|
||||
response.ShouldNotBeNull();
|
||||
|
||||
var received = probe.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(3));
|
||||
var received = probe.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
received.DeploymentId.ShouldBe(DeploymentId.Parse(request.DeploymentId));
|
||||
received.NodeId.Value.ShouldBe("site-a-1");
|
||||
received.Outcome.ShouldBe(ApplyAckOutcome.Failed);
|
||||
@@ -73,12 +73,12 @@ public sealed class CentralCommandGrpcServiceTests : TestKit
|
||||
var service = Service(registry);
|
||||
|
||||
await service.ReportApply(WellFormed(), new FakeServerCallContext());
|
||||
probe.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(3)).FailureReason.ShouldBeNull();
|
||||
probe.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken).FailureReason.ShouldBeNull();
|
||||
|
||||
var blank = WellFormed();
|
||||
blank.FailureReason = "";
|
||||
await service.ReportApply(blank, new FakeServerCallContext());
|
||||
probe.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(3)).FailureReason.ShouldBe("");
|
||||
probe.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken).FailureReason.ShouldBe("");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -109,7 +109,7 @@ public sealed class CentralCommandGrpcServiceTests : TestKit
|
||||
Service(registry).ReportApply(request, new FakeServerCallContext()));
|
||||
|
||||
ex.StatusCode.ShouldBe(StatusCode.InvalidArgument);
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -126,7 +126,7 @@ public sealed class CentralCommandGrpcServiceTests : TestKit
|
||||
Service(registry).ReportApply(request, new FakeServerCallContext()));
|
||||
|
||||
ex.StatusCode.ShouldBe(StatusCode.InvalidArgument);
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -145,7 +145,7 @@ public sealed class CentralCommandGrpcServiceTests : TestKit
|
||||
Service(registry).ReportApply(request, new FakeServerCallContext()));
|
||||
|
||||
ex.StatusCode.ShouldBe(StatusCode.InvalidArgument);
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -55,10 +55,10 @@ public sealed class DeployedDriverSetTests
|
||||
{
|
||||
db.Deployments.Add(Dep(d1, DeploymentStatus.Sealed, Now.AddHours(-1), Artifact("drv-quiet", "drv-kept")));
|
||||
db.NodeDeploymentStates.Add(Node("n1", d1, NodeDeploymentStatus.Applied, Now.AddHours(-1)));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var deployed = await new DeployedDriverSet(factory, parseDriverIds: Parse).LoadAsync();
|
||||
var deployed = await new DeployedDriverSet(factory, parseDriverIds: Parse).LoadAsync(TestContext.Current.CancellationToken);
|
||||
deployed.ShouldNotBeNull();
|
||||
|
||||
var store = new InMemoryDriverStatusSnapshotStore();
|
||||
@@ -89,10 +89,10 @@ public sealed class DeployedDriverSetTests
|
||||
db.NodeDeploymentStates.Add(Node("n1", stale, NodeDeploymentStatus.Applied, Now.AddDays(-9)));
|
||||
db.NodeDeploymentStates.Add(Node("n1", old, NodeDeploymentStatus.Applied, Now.AddHours(-3)));
|
||||
db.NodeDeploymentStates.Add(Node("n1", failed, NodeDeploymentStatus.Failed, Now.AddHours(-1)));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var deployed = await new DeployedDriverSet(factory, parseDriverIds: Parse).LoadAsync();
|
||||
var deployed = await new DeployedDriverSet(factory, parseDriverIds: Parse).LoadAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
// n1's latest (failed apply, may be partly running), its latest Applied (what it ran before), and the
|
||||
// latest Sealed deployment — but not a superseded one nobody runs.
|
||||
@@ -108,13 +108,13 @@ public sealed class DeployedDriverSetTests
|
||||
await using (var db = factory.CreateDbContext())
|
||||
{
|
||||
db.Deployments.Add(Dep(d1, DeploymentStatus.Sealed, Now, Artifact("drv-a")));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var parses = 0;
|
||||
var set = new DeployedDriverSet(factory, parseDriverIds: b => { parses++; return Parse(b); });
|
||||
await set.LoadAsync();
|
||||
await set.LoadAsync();
|
||||
await set.LoadAsync(TestContext.Current.CancellationToken);
|
||||
await set.LoadAsync(TestContext.Current.CancellationToken);
|
||||
parses.ShouldBe(1);
|
||||
set.CachedDeployments.ShouldBe(1);
|
||||
|
||||
@@ -122,10 +122,10 @@ public sealed class DeployedDriverSetTests
|
||||
await using (var db = factory.CreateDbContext())
|
||||
{
|
||||
db.Deployments.Add(Dep(d2, DeploymentStatus.Sealed, Now.AddMinutes(1), Artifact("drv-b")));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
(await set.LoadAsync())!.ShouldBe(["drv-b"]);
|
||||
(await set.LoadAsync(TestContext.Current.CancellationToken))!.ShouldBe(["drv-b"]);
|
||||
set.CachedDeployments.ShouldBe(1); // d1 is no longer live, so it is pruned
|
||||
parses.ShouldBe(2);
|
||||
}
|
||||
@@ -138,10 +138,10 @@ public sealed class DeployedDriverSetTests
|
||||
await using (var db = factory.CreateDbContext())
|
||||
{
|
||||
db.Deployments.Add(Dep(d1, DeploymentStatus.Sealed, Now, Array.Empty<byte>())); // retention-emptied
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
(await new DeployedDriverSet(factory, parseDriverIds: Parse).LoadAsync()).ShouldBeNull();
|
||||
(await new DeployedDriverSet(factory, parseDriverIds: Parse).LoadAsync(TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class TelemetryModeWiringTests
|
||||
public async Task Dps_mode_spawns_the_four_telemetry_bridges_and_not_the_dial_supervisor()
|
||||
{
|
||||
using var host = BuildBridgeHost(TelemetryDialOptions.ModeDps);
|
||||
await host.StartAsync();
|
||||
await host.StartAsync(TestContext.Current.CancellationToken);
|
||||
try
|
||||
{
|
||||
var registry = host.Services.GetRequiredService<ActorRegistry>();
|
||||
@@ -39,7 +39,7 @@ public sealed class TelemetryModeWiringTests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await host.StopAsync();
|
||||
await host.StopAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class TelemetryModeWiringTests
|
||||
public async Task Grpc_mode_spawns_the_dial_supervisor_and_none_of_the_four_telemetry_bridges()
|
||||
{
|
||||
using var host = BuildBridgeHost(TelemetryDialOptions.ModeGrpc);
|
||||
await host.StartAsync();
|
||||
await host.StartAsync(TestContext.Current.CancellationToken);
|
||||
try
|
||||
{
|
||||
var registry = host.Services.GetRequiredService<ActorRegistry>();
|
||||
@@ -62,7 +62,7 @@ public sealed class TelemetryModeWiringTests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await host.StopAsync();
|
||||
await host.StopAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public sealed class TelemetryModeWiringTests
|
||||
public async Task Default_mode_is_dps()
|
||||
{
|
||||
using var host = BuildBridgeHost(mode: null);
|
||||
await host.StartAsync();
|
||||
await host.StartAsync(TestContext.Current.CancellationToken);
|
||||
try
|
||||
{
|
||||
var registry = host.Services.GetRequiredService<ActorRegistry>();
|
||||
@@ -80,7 +80,7 @@ public sealed class TelemetryModeWiringTests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await host.StopAsync();
|
||||
await host.StopAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ public sealed class RawBrowseRebirthRefreshTests : IDisposable
|
||||
gate.SetResult(new BrowseOpenResult(true, null, stale));
|
||||
await open;
|
||||
|
||||
await svc.Closed.Task.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await svc.Closed.Task.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
svc.ClosedTokens.ShouldBe([stale]);
|
||||
Get<Guid>(modal, "_token").ShouldBe(Guid.Empty);
|
||||
}
|
||||
|
||||
@@ -68,8 +68,8 @@ public sealed class RawCsvRenderingTests
|
||||
var dbName = Uns.RawTreeTestDb.SeedFresh();
|
||||
var reader = new RawTagCsvExportReader(Uns.RawTreeTestDb.Factory(dbName));
|
||||
|
||||
var full = await RawTagCsvExport.BuildAsync(reader, Uns.RawTreeTestDb.FolderedDeviceId, null, "Modbus");
|
||||
var preview = await RawTagCsvExport.BuildAsync(reader, Uns.RawTreeTestDb.FolderedDeviceId, null, "Modbus", maxRows: 1);
|
||||
var full = await RawTagCsvExport.BuildAsync(reader, Uns.RawTreeTestDb.FolderedDeviceId, null, "Modbus", TestContext.Current.CancellationToken);
|
||||
var preview = await RawTagCsvExport.BuildAsync(reader, Uns.RawTreeTestDb.FolderedDeviceId, null, "Modbus", ct: TestContext.Current.CancellationToken, maxRows: 1);
|
||||
|
||||
full!.RowCount.ShouldBe(2);
|
||||
Commons.Csv.CsvParser.Parse(full.Csv).Count.ShouldBe(3); // header + 2 (positive control)
|
||||
|
||||
+3
-3
@@ -50,9 +50,9 @@ public sealed class AnalysisCompilationReuseTests
|
||||
// A clean script stays clean and an error stays an error across interleaved analyses on one service
|
||||
// — a stale tree leaking from the previous call would surface here.
|
||||
var svc = new ScriptAnalysisService();
|
||||
svc.Diagnose(new DiagnoseRequest("return ctx.Nope();")).Markers.ShouldContain(m => m.Severity == 8);
|
||||
svc.Diagnose(new DiagnoseRequest("""return (double)ctx.GetTag("A").Value * 2.0;""")).Markers.ShouldBeEmpty();
|
||||
svc.Diagnose(new DiagnoseRequest("var a = 1;\nreturn ctx.Nope();")).Markers
|
||||
svc.Diagnose(new DiagnoseRequest("return ctx.Nope();"), TestContext.Current.CancellationToken).Markers.ShouldContain(m => m.Severity == 8);
|
||||
svc.Diagnose(new DiagnoseRequest("""return (double)ctx.GetTag("A").Value * 2.0;"""), TestContext.Current.CancellationToken).Markers.ShouldBeEmpty();
|
||||
svc.Diagnose(new DiagnoseRequest("var a = 1;\nreturn ctx.Nope();"), TestContext.Current.CancellationToken).Markers
|
||||
.ShouldContain(m => m.Severity == 8 && m.StartLineNumber == 2);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -23,34 +23,34 @@ public sealed class CtxCompletionGuardTests
|
||||
[Fact] public async Task Completion_on_ctx_GetTag_literal_offers_catalog_paths()
|
||||
{
|
||||
// "return ctx.GetTag(" + '""' + ')' — col 20 lands on the closing quote of the empty string literal.
|
||||
var res = await Svc.CompleteAsync(new CompletionsRequest("return ctx.GetTag(\"\")", 1, 20));
|
||||
var res = await Svc.CompleteAsync(new CompletionsRequest("return ctx.GetTag(\"\")", 1, 20), TestContext.Current.CancellationToken);
|
||||
res.Items.ShouldContain(i => i.InsertText == "Line1.Speed");
|
||||
}
|
||||
|
||||
[Fact] public async Task Completion_on_non_ctx_receiver_does_NOT_offer_catalog_paths()
|
||||
{
|
||||
// Same shape but receiver is "foo" — must NOT offer catalog paths.
|
||||
var res = await Svc.CompleteAsync(new CompletionsRequest("return foo.GetTag(\"\")", 1, 20));
|
||||
var res = await Svc.CompleteAsync(new CompletionsRequest("return foo.GetTag(\"\")", 1, 20), TestContext.Current.CancellationToken);
|
||||
res.Items.ShouldNotContain(i => i.InsertText == "Line1.Speed");
|
||||
}
|
||||
|
||||
[Fact] public async Task Hover_on_ctx_SetVirtualTag_literal_warns_write_is_dropped()
|
||||
{
|
||||
var md = (await Svc.Hover(new HoverRequest("return ctx.SetVirtualTag(\"V\", 1);", 1, 27))).Markdown;
|
||||
var md = (await Svc.Hover(new HoverRequest("return ctx.SetVirtualTag(\"V\", 1);", 1, 27), TestContext.Current.CancellationToken)).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("single-tag mode");
|
||||
}
|
||||
|
||||
[Fact] public async Task Hover_on_ctx_GetTag_literal_has_no_dropped_write_note()
|
||||
{
|
||||
var md = (await Svc.Hover(new HoverRequest("return ctx.GetTag(\"V\").Value;", 1, 20))).Markdown;
|
||||
var md = (await Svc.Hover(new HoverRequest("return ctx.GetTag(\"V\").Value;", 1, 20), TestContext.Current.CancellationToken)).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldNotContain("single-tag mode");
|
||||
}
|
||||
|
||||
[Fact] public async Task Hover_on_non_ctx_receiver_literal_is_not_treated_as_a_tag_path()
|
||||
{
|
||||
var md = (await Svc.Hover(new HoverRequest("return bar.GetTag(\"V\");", 1, 20))).Markdown;
|
||||
var md = (await Svc.Hover(new HoverRequest("return bar.GetTag(\"V\");", 1, 20), TestContext.Current.CancellationToken)).Markdown;
|
||||
(md is null || !md.Contains("Tag path")).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -65,7 +65,7 @@ public sealed class EquipTokenEditorTests
|
||||
[Fact] public async Task Hover_on_equip_token_literal_notes_equipment_relative()
|
||||
{
|
||||
// caret inside ctx.GetTag("{{equip}}/Speed")
|
||||
var md = (await Svc.Hover(new HoverRequest("""return ctx.GetTag("{{equip}}/Speed").Value;""", 1, 24))).Markdown;
|
||||
var md = (await Svc.Hover(new HoverRequest("""return ctx.GetTag("{{equip}}/Speed").Value;""", 1, 24), TestContext.Current.CancellationToken)).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("Equipment-relative");
|
||||
// the {{equip}} token must render literally (non-interpolated markdown segment)
|
||||
@@ -78,14 +78,14 @@ public sealed class EquipTokenEditorTests
|
||||
// "Missing" is not one of the equipment's references (Speed/Temp) → a diagnostic, matching the
|
||||
// deploy gate. Uses the equipment context via the request's EquipmentId.
|
||||
var resp = await Svc.DiagnoseAsync(
|
||||
new DiagnoseRequest("""return ctx.GetTag("{{equip}}/Missing").Value;""", "EQ-1"));
|
||||
new DiagnoseRequest("""return ctx.GetTag("{{equip}}/Missing").Value;""", "EQ-1"), TestContext.Current.CancellationToken);
|
||||
resp.Markers.ShouldContain(m => m.Code == "OTSCRIPT_EQUIPREF" && m.Message.Contains("Missing"));
|
||||
}
|
||||
|
||||
[Fact] public async Task Diagnostic_clean_for_resolved_equip_reference()
|
||||
{
|
||||
var resp = await Svc.DiagnoseAsync(
|
||||
new DiagnoseRequest("""return ctx.GetTag("{{equip}}/Speed").Value;""", "EQ-1"));
|
||||
new DiagnoseRequest("""return ctx.GetTag("{{equip}}/Speed").Value;""", "EQ-1"), TestContext.Current.CancellationToken);
|
||||
resp.Markers.ShouldNotContain(m => m.Code == "OTSCRIPT_EQUIPREF");
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public sealed class EquipTokenEditorTests
|
||||
// No EquipmentId (shared ScriptEdit page) → the equip-ref check is inert (base markers only),
|
||||
// matching the deploy gate which only resolves per owning equipment.
|
||||
var resp = await Svc.DiagnoseAsync(
|
||||
new DiagnoseRequest("""return ctx.GetTag("{{equip}}/Missing").Value;"""));
|
||||
new DiagnoseRequest("""return ctx.GetTag("{{equip}}/Missing").Value;"""), TestContext.Current.CancellationToken);
|
||||
resp.Markers.ShouldNotContain(m => m.Code == "OTSCRIPT_EQUIPREF");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,18 +23,18 @@ public sealed class HoverSignatureTests
|
||||
[Fact] public async Task Hover_on_GetTag_returns_member_markdown()
|
||||
{
|
||||
// hover over the "GetTag" identifier in ctx.GetTag("A")
|
||||
var md = (await Svc.Hover(new HoverRequest("""return ctx.GetTag("A").Value;""", 1, 16))).Markdown;
|
||||
var md = (await Svc.Hover(new HoverRequest("""return ctx.GetTag("A").Value;""", 1, 16), TestContext.Current.CancellationToken)).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("GetTag");
|
||||
}
|
||||
|
||||
[Fact] public async Task Hover_on_nothing_returns_null()
|
||||
=> (await Svc.Hover(new HoverRequest(" ", 1, 1))).Markdown.ShouldBeNull();
|
||||
=> (await Svc.Hover(new HoverRequest(" ", 1, 1), TestContext.Current.CancellationToken)).Markdown.ShouldBeNull();
|
||||
|
||||
[Fact] public void SignatureHelp_inside_GetTag_call_shows_the_signature()
|
||||
{
|
||||
// caret just after the open paren of ctx.GetTag(
|
||||
var sh = Svc.SignatureHelp(new SignatureHelpRequest("""return ctx.GetTag();""", 1, 19));
|
||||
var sh = Svc.SignatureHelp(new SignatureHelpRequest("""return ctx.GetTag();""", 1, 19), TestContext.Current.CancellationToken);
|
||||
sh.Label.ShouldNotBeNull();
|
||||
sh.Label!.ShouldContain("GetTag");
|
||||
sh.Parameters.ShouldNotBeNull();
|
||||
@@ -44,7 +44,7 @@ public sealed class HoverSignatureTests
|
||||
[Fact] public async Task Hover_on_a_plain_local_resolves_the_local_not_an_enclosing_member()
|
||||
{
|
||||
// hovering the local `x` (not a member) must resolve the LOCAL, and must NOT climb to GetTag/Value.
|
||||
var md = (await Svc.Hover(new HoverRequest("var x = 1;\nreturn ctx.GetTag(\"A\").Value + x;", 2, 33))).Markdown;
|
||||
var md = (await Svc.Hover(new HoverRequest("var x = 1;\nreturn ctx.GetTag(\"A\").Value + x;", 2, 33), TestContext.Current.CancellationToken)).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("x");
|
||||
md.ShouldNotContain("GetTag");
|
||||
@@ -54,12 +54,12 @@ public sealed class HoverSignatureTests
|
||||
{
|
||||
// ctx.Now is a property, not a call; use a parameterless method if one exists, else assert empty-safe behavior.
|
||||
// Deadband is static on ScriptContext (3 params) — instead verify a 0-arg edge via an empty arg list doesn't throw:
|
||||
var sh = Svc.SignatureHelp(new SignatureHelpRequest("return ctx.GetTag();", 1, 19));
|
||||
var sh = Svc.SignatureHelp(new SignatureHelpRequest("return ctx.GetTag();", 1, 19), TestContext.Current.CancellationToken);
|
||||
sh.ActiveParameter.ShouldBe(0); // clamped; no exception
|
||||
}
|
||||
|
||||
[Fact] public async Task Hover_with_out_of_range_position_returns_null_without_throwing()
|
||||
=> (await Svc.Hover(new HoverRequest("return 1;", 99, 99))).Markdown.ShouldBeNull();
|
||||
=> (await Svc.Hover(new HoverRequest("return 1;", 99, 99), TestContext.Current.CancellationToken)).Markdown.ShouldBeNull();
|
||||
|
||||
// ── tag-path hover (driven by the catalog) ─────────────────────────────────────────────────
|
||||
|
||||
@@ -67,7 +67,7 @@ public sealed class HoverSignatureTests
|
||||
{
|
||||
var svc = new ScriptAnalysisService(new FakeCatalog());
|
||||
// caret on the path literal inside ctx.GetTag("Line1.Speed")
|
||||
var md = (await svc.Hover(new HoverRequest("""return ctx.GetTag("Line1.Speed").Value;""", 1, 24))).Markdown;
|
||||
var md = (await svc.Hover(new HoverRequest("""return ctx.GetTag("Line1.Speed").Value;""", 1, 24), TestContext.Current.CancellationToken)).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("Line1.Speed");
|
||||
md.ShouldContain("Double");
|
||||
@@ -79,7 +79,7 @@ public sealed class HoverSignatureTests
|
||||
{
|
||||
var svc = new ScriptAnalysisService(new FakeCatalog());
|
||||
// caret on the path literal inside ctx.GetTag("Unknownz")
|
||||
var md = (await svc.Hover(new HoverRequest("""return ctx.GetTag("Unknownz").Value;""", 1, 22))).Markdown;
|
||||
var md = (await svc.Hover(new HoverRequest("""return ctx.GetTag("Unknownz").Value;""", 1, 22), TestContext.Current.CancellationToken)).Markdown;
|
||||
md.ShouldNotBeNull();
|
||||
md!.ShouldContain("Not a known");
|
||||
}
|
||||
|
||||
+3
-3
@@ -26,7 +26,7 @@ public sealed class MarkerPayloadBoundTests
|
||||
[Fact]
|
||||
public void A_non_csharp_paste_returns_a_bounded_marker_set_that_still_holds_the_errors()
|
||||
{
|
||||
var markers = new ScriptAnalysisService().Diagnose(new DiagnoseRequest(NonCSharpPaste())).Markers;
|
||||
var markers = new ScriptAnalysisService().Diagnose(new DiagnoseRequest(NonCSharpPaste()), TestContext.Current.CancellationToken).Markers;
|
||||
|
||||
markers.Count.ShouldBe(ScriptEditorLimits.MaxDiagnosticMarkers); // positive control: the cap was hit
|
||||
markers.ShouldAllBe(m => m.Message.Length <= ScriptEditorLimits.MaxDiagnosticMessageChars);
|
||||
@@ -39,7 +39,7 @@ public sealed class MarkerPayloadBoundTests
|
||||
public void A_long_quoted_token_has_its_message_truncated()
|
||||
{
|
||||
var giant = new string('x', 20_000);
|
||||
var markers = new ScriptAnalysisService().Diagnose(new DiagnoseRequest($"return {giant};")).Markers;
|
||||
var markers = new ScriptAnalysisService().Diagnose(new DiagnoseRequest($"return {giant};"), TestContext.Current.CancellationToken).Markers;
|
||||
|
||||
markers.ShouldNotBeEmpty();
|
||||
markers.ShouldAllBe(m => m.Message.Length <= ScriptEditorLimits.MaxDiagnosticMessageChars);
|
||||
@@ -50,7 +50,7 @@ public sealed class MarkerPayloadBoundTests
|
||||
public void A_small_script_is_not_truncated()
|
||||
{
|
||||
// Positive control for the cap: ordinary diagnostics pass through untouched.
|
||||
var markers = new ScriptAnalysisService().Diagnose(new DiagnoseRequest("return ctx.Nope();")).Markers;
|
||||
var markers = new ScriptAnalysisService().Diagnose(new DiagnoseRequest("return ctx.Nope();"), TestContext.Current.CancellationToken).Markers;
|
||||
markers.ShouldContain(m => m.Severity == 8 && !m.Message.EndsWith("…"));
|
||||
}
|
||||
|
||||
|
||||
+28
-28
@@ -168,7 +168,7 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
var paths = await catalog.GetPathsAsync(null, default);
|
||||
var paths = await catalog.GetPathsAsync(null, TestContext.Current.CancellationToken);
|
||||
|
||||
paths.ShouldContain("Plant/Modbus/dev1/Speed"); // directly under the device
|
||||
paths.ShouldContain("Plant/Modbus/dev1/Motors/Torque"); // nested under a tag group
|
||||
@@ -190,7 +190,7 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
var paths = await catalog.GetPathsAsync(null, default);
|
||||
var paths = await catalog.GetPathsAsync(null, TestContext.Current.CancellationToken);
|
||||
|
||||
paths.Distinct(StringComparer.OrdinalIgnoreCase).Count().ShouldBe(paths.Count);
|
||||
}
|
||||
@@ -203,7 +203,7 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
var paths = await catalog.GetPathsAsync("Plant/Modbus/dev1/", default);
|
||||
var paths = await catalog.GetPathsAsync("Plant/Modbus/dev1/", TestContext.Current.CancellationToken);
|
||||
|
||||
paths.ShouldContain("Plant/Modbus/dev1/Speed");
|
||||
paths.ShouldContain("Plant/Modbus/dev1/Motors/Torque");
|
||||
@@ -218,7 +218,7 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
(await catalog.GetPathsAsync("plant/modbus", default)).ShouldContain("Plant/Modbus/dev1/Speed");
|
||||
(await catalog.GetPathsAsync("plant/modbus", TestContext.Current.CancellationToken)).ShouldContain("Plant/Modbus/dev1/Speed");
|
||||
}
|
||||
|
||||
/// <summary>A tag whose ancestry chain is broken (device row missing) has no RawPath. The runtime could
|
||||
@@ -302,7 +302,7 @@ public sealed class ScriptTagCatalogTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
(await catalog.GetPathsAsync(null, default)).ShouldContain("RootDrv/d0/Scalar");
|
||||
(await catalog.GetPathsAsync(null, TestContext.Current.CancellationToken)).ShouldContain("RootDrv/d0/Scalar");
|
||||
}
|
||||
|
||||
/// <summary>An empty database yields an empty list rather than throwing.</summary>
|
||||
@@ -311,7 +311,7 @@ public sealed class ScriptTagCatalogTests
|
||||
{
|
||||
var (catalog, _) = Fresh();
|
||||
|
||||
(await catalog.GetPathsAsync(null, default)).ShouldBeEmpty();
|
||||
(await catalog.GetPathsAsync(null, TestContext.Current.CancellationToken)).ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>Hover: an absolute RawPath resolves to Tag-kind info carrying the configured DataType and —
|
||||
@@ -322,7 +322,7 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
var info = await catalog.GetTagInfoAsync("Plant/Modbus/dev1/Motors/Torque", default);
|
||||
var info = await catalog.GetTagInfoAsync("Plant/Modbus/dev1/Motors/Torque", TestContext.Current.CancellationToken);
|
||||
|
||||
info.ShouldNotBeNull();
|
||||
info!.Path.ShouldBe("Plant/Modbus/dev1/Motors/Torque");
|
||||
@@ -339,8 +339,8 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
(await catalog.GetTagInfoAsync("Computed", default)).ShouldBeNull();
|
||||
var info = await catalog.GetTagInfoAsync("EQ-1/Computed", default);
|
||||
(await catalog.GetTagInfoAsync("Computed", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
var info = await catalog.GetTagInfoAsync("EQ-1/Computed", TestContext.Current.CancellationToken);
|
||||
|
||||
info.ShouldNotBeNull();
|
||||
info!.Kind.ShouldBe("Virtual tag");
|
||||
@@ -356,7 +356,7 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
(await catalog.GetTagInfoAsync("Motor.Speed", default)).ShouldBeNull();
|
||||
(await catalog.GetTagInfoAsync("Motor.Speed", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>An unknown path resolves to null.</summary>
|
||||
@@ -366,7 +366,7 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
(await catalog.GetTagInfoAsync("Plant/Modbus/dev1/NoSuchTag", default)).ShouldBeNull();
|
||||
(await catalog.GetTagInfoAsync("Plant/Modbus/dev1/NoSuchTag", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>The lookup is case-SENSITIVE (Ordinal), mirroring the runtime DependencyMuxActor's
|
||||
@@ -378,8 +378,8 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts) = Fresh();
|
||||
Seed(opts);
|
||||
|
||||
(await catalog.GetTagInfoAsync("plant/modbus/dev1/speed", default)).ShouldBeNull();
|
||||
(await catalog.GetTagInfoAsync("Plant/Modbus/dev1/Speed", default)).ShouldNotBeNull();
|
||||
(await catalog.GetTagInfoAsync("plant/modbus/dev1/speed", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
(await catalog.GetTagInfoAsync("Plant/Modbus/dev1/Speed", TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -396,8 +396,8 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts, factory, _) = FreshInstrumented();
|
||||
Seed(opts);
|
||||
|
||||
var first = await catalog.GetPathsAsync(null, default);
|
||||
var second = await catalog.GetPathsAsync(null, default);
|
||||
var first = await catalog.GetPathsAsync(null, TestContext.Current.CancellationToken);
|
||||
var second = await catalog.GetPathsAsync(null, TestContext.Current.CancellationToken);
|
||||
|
||||
factory.Created.ShouldBe(1);
|
||||
second.ShouldBe(first);
|
||||
@@ -412,7 +412,7 @@ public sealed class ScriptTagCatalogTests
|
||||
var (catalog, opts, factory, time) = FreshInstrumented();
|
||||
Seed(opts);
|
||||
|
||||
(await catalog.GetPathsAsync(null, default)).ShouldNotContain("Plant/Modbus/dev1/Pressure");
|
||||
(await catalog.GetPathsAsync(null, TestContext.Current.CancellationToken)).ShouldNotContain("Plant/Modbus/dev1/Pressure");
|
||||
factory.Created.ShouldBe(1);
|
||||
|
||||
using (var db = new OtOpcUaConfigDbContext(opts))
|
||||
@@ -427,7 +427,7 @@ public sealed class ScriptTagCatalogTests
|
||||
|
||||
time.Advance(TimeSpan.FromSeconds(6));
|
||||
|
||||
var after = await catalog.GetPathsAsync(null, default);
|
||||
var after = await catalog.GetPathsAsync(null, TestContext.Current.CancellationToken);
|
||||
|
||||
factory.Created.ShouldBe(2);
|
||||
after.ShouldContain("Plant/Modbus/dev1/Pressure");
|
||||
@@ -445,19 +445,19 @@ public sealed class ScriptTagCatalogTests
|
||||
factory.Gate = gate;
|
||||
|
||||
var first = Task.Run(() => catalog.GetPathsAsync(null, default));
|
||||
await factory.FirstRequest.Task.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
await factory.FirstRequest.Task.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
|
||||
var second = Task.Run(() => catalog.GetPathsAsync(null, default));
|
||||
|
||||
// Give the second caller ample time to reach the cache. It must be parked on the rebuild gate,
|
||||
// NOT scanning: a second scan here is exactly the duplicated work this test falsifies.
|
||||
for (var i = 0; i < 50 && factory.Created < 2; i++) await Task.Delay(10);
|
||||
for (var i = 0; i < 50 && factory.Created < 2; i++) await Task.Delay(10, TestContext.Current.CancellationToken);
|
||||
factory.Created.ShouldBe(1);
|
||||
|
||||
gate.SetResult();
|
||||
|
||||
var a = await first.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
var b = await second.WaitAsync(TimeSpan.FromSeconds(10));
|
||||
var a = await first.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
var b = await second.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
|
||||
factory.Created.ShouldBe(1);
|
||||
a.ShouldContain("Plant/Modbus/dev1/Speed");
|
||||
@@ -473,14 +473,14 @@ public sealed class ScriptTagCatalogTests
|
||||
var cache = new ScriptTagCatalogSnapshotCache(time);
|
||||
IReadOnlyList<ScriptTagInfo> good = [new ScriptTagInfo("A/B", "Tag", "Float", "DRV-1")];
|
||||
|
||||
(await cache.GetOrBuildAsync(_ => Task.FromResult(good), default)).ShouldBe(good);
|
||||
(await cache.GetOrBuildAsync(_ => Task.FromResult(good), TestContext.Current.CancellationToken)).ShouldBe(good);
|
||||
|
||||
time.Advance(TimeSpan.FromSeconds(6));
|
||||
await Should.ThrowAsync<InvalidOperationException>(
|
||||
cache.GetOrBuildAsync(_ => Task.FromException<IReadOnlyList<ScriptTagInfo>>(new InvalidOperationException("boom")), default));
|
||||
cache.GetOrBuildAsync(_ => Task.FromException<IReadOnlyList<ScriptTagInfo>>(new InvalidOperationException("boom")), TestContext.Current.CancellationToken));
|
||||
|
||||
// The gate was released and the old snapshot survived, so the next build still runs and succeeds.
|
||||
(await cache.GetOrBuildAsync(_ => Task.FromResult(good), default)).ShouldBe(good);
|
||||
(await cache.GetOrBuildAsync(_ => Task.FromResult(good), TestContext.Current.CancellationToken)).ShouldBe(good);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -514,7 +514,7 @@ public sealed class ScriptTagCatalogTests
|
||||
Seed(opts);
|
||||
SeedReferences(opts);
|
||||
|
||||
var names = await catalog.GetEquipmentReferenceNamesAsync("EQ-1", null, default);
|
||||
var names = await catalog.GetEquipmentReferenceNamesAsync("EQ-1", null, TestContext.Current.CancellationToken);
|
||||
|
||||
names.ShouldContain("Speed"); // TAG-EQ Name, no override
|
||||
names.ShouldContain("Path"); // TAG-SP under the "Path" override
|
||||
@@ -529,7 +529,7 @@ public sealed class ScriptTagCatalogTests
|
||||
Seed(opts);
|
||||
SeedReferences(opts);
|
||||
|
||||
var names = await catalog.GetEquipmentReferenceNamesAsync("EQ-1", "sp", default);
|
||||
var names = await catalog.GetEquipmentReferenceNamesAsync("EQ-1", "sp", TestContext.Current.CancellationToken);
|
||||
|
||||
names.ShouldContain("Speed");
|
||||
names.ShouldNotContain("Path");
|
||||
@@ -543,7 +543,7 @@ public sealed class ScriptTagCatalogTests
|
||||
Seed(opts);
|
||||
SeedReferences(opts);
|
||||
|
||||
(await catalog.GetEquipmentReferenceNamesAsync("EQ-UNKNOWN", null, default)).ShouldBeEmpty();
|
||||
(await catalog.GetEquipmentReferenceNamesAsync("", null, default)).ShouldBeEmpty();
|
||||
(await catalog.GetEquipmentReferenceNamesAsync("EQ-UNKNOWN", null, TestContext.Current.CancellationToken)).ShouldBeEmpty();
|
||||
(await catalog.GetEquipmentReferenceNamesAsync("", null, TestContext.Current.CancellationToken)).ShouldBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class ScriptListQueriesTests
|
||||
var name = await SeedAsync();
|
||||
await using var db = NewDb(name);
|
||||
|
||||
var rows = await ScriptListQueries.LoadRowsAsync(db);
|
||||
var rows = await ScriptListQueries.LoadRowsAsync(db, TestContext.Current.CancellationToken);
|
||||
|
||||
rows.Select(r => r.ScriptId).ShouldBe(["s-a", "s-b"]);
|
||||
rows[0].ShouldBe(new ScriptListRow("s-a", "Alpha", "CSharp", "ha"));
|
||||
@@ -45,8 +45,8 @@ public sealed class ScriptListQueriesTests
|
||||
var name = await SeedAsync();
|
||||
await using var db = NewDb(name);
|
||||
|
||||
(await ScriptListQueries.LoadSourceAsync(db, "s-b")).ShouldBe("return 2;");
|
||||
(await ScriptListQueries.LoadSourceAsync(db, "missing")).ShouldBeNull();
|
||||
(await ScriptListQueries.LoadSourceAsync(db, "s-b", TestContext.Current.CancellationToken)).ShouldBe("return 2;");
|
||||
(await ScriptListQueries.LoadSourceAsync(db, "missing", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Akka.Actor;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.AdminUI.Hubs;
|
||||
@@ -39,7 +39,7 @@ public sealed class TelemetryDialDeadlineTests : TestKit
|
||||
first.Ct.IsCancellationRequested.ShouldBeFalse("the deadline has not elapsed yet");
|
||||
|
||||
// The stalled dial is cut at the deadline...
|
||||
AwaitCondition(() => first.Ct.IsCancellationRequested, Timeout, PollInterval);
|
||||
AwaitCondition(() => first.Ct.IsCancellationRequested, Timeout, PollInterval, cancellationToken: TestContext.Current.CancellationToken);
|
||||
// ...and the supervisor re-dials (a stalled dial is a transport failure, never a silent end).
|
||||
var invocations = dial.WaitForCount(2, Timeout);
|
||||
invocations[1].CorrelationId.ShouldBe("central-node-1-2");
|
||||
@@ -53,10 +53,10 @@ public sealed class TelemetryDialDeadlineTests : TestKit
|
||||
Spawn(dial, callTimeoutSeconds: 1, sinks);
|
||||
|
||||
var first = dial.WaitForCount(1, Timeout).Single();
|
||||
AwaitCondition(() => sinks.HealthStore.Upserts.Count == 1, Timeout, PollInterval);
|
||||
AwaitCondition(() => sinks.HealthStore.Upserts.Count == 1, Timeout, PollInterval, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Positive control: well past the deadline, the established stream is untouched.
|
||||
await Task.Delay(TimeSpan.FromSeconds(2.5));
|
||||
await Task.Delay(TimeSpan.FromSeconds(2.5), TestContext.Current.CancellationToken);
|
||||
first.Ct.IsCancellationRequested.ShouldBeFalse("an established stream must never be cut by the dial deadline");
|
||||
dial.Invocations.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
+14
-14
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Akka.Actor;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Shouldly;
|
||||
@@ -70,14 +70,14 @@ public sealed class TelemetryDialSupervisorTests : TestKit
|
||||
actor.Tell(new TelemetryDialSupervisor.RefreshNodes());
|
||||
|
||||
// node-1's dialer is cancelled and dropped.
|
||||
AwaitCondition(() => node1.Ct.IsCancellationRequested, Timeout, PollInterval);
|
||||
AwaitCondition(() => node1.Ct.IsCancellationRequested, Timeout, PollInterval, cancellationToken: TestContext.Current.CancellationToken);
|
||||
// node-3 gets a fresh dialer.
|
||||
var invocations = dial.WaitForCount(3, Timeout);
|
||||
invocations.ShouldContain(i => i.Target.NodeId == "node-3" && i.CorrelationId == "central-node-3-1");
|
||||
|
||||
// node-2 is present in BOTH refreshes with an unchanged endpoint: it must NOT be restarted, or
|
||||
// its live stream would be dropped on every refresh. Exactly one dial invocation for it.
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken);
|
||||
dial.Invocations.Count(i => i.Target.NodeId == "node-2").ShouldBe(1);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public sealed class TelemetryDialSupervisorTests : TestKit
|
||||
actor.Tell(new TelemetryDialSupervisor.RefreshNodes());
|
||||
|
||||
// The old node-1 stream is torn down and a fresh dialer opens at the NEW endpoint (gen 2).
|
||||
AwaitCondition(() => node1Original.Ct.IsCancellationRequested, Timeout, PollInterval);
|
||||
AwaitCondition(() => node1Original.Ct.IsCancellationRequested, Timeout, PollInterval, cancellationToken: TestContext.Current.CancellationToken);
|
||||
var invocations = dial.WaitForCount(3, Timeout);
|
||||
invocations.ShouldContain(i =>
|
||||
i.Target.NodeId == "node-1"
|
||||
@@ -110,7 +110,7 @@ public sealed class TelemetryDialSupervisorTests : TestKit
|
||||
&& i.CorrelationId == "central-node-1-2");
|
||||
|
||||
// node-2's endpoint did not change → still exactly one dial invocation (no churn).
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken);
|
||||
dial.Invocations.Count(i => i.Target.NodeId == "node-2").ShouldBe(1);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ public sealed class TelemetryDialSupervisorTests : TestKit
|
||||
sinks.Health.ShouldHaveSingleItem().DriverInstanceId.ShouldBe("d1");
|
||||
sinks.Resilience.ShouldHaveSingleItem().DriverInstanceId.ShouldBe("d1");
|
||||
},
|
||||
Timeout);
|
||||
Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -173,7 +173,7 @@ public sealed class TelemetryDialSupervisorTests : TestKit
|
||||
first.OnMapped(Alarm("stale"));
|
||||
|
||||
// Give the message a chance to be (wrongly) routed, then assert it was not.
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
sinks.Alarms.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ public sealed class TelemetryDialSupervisorTests : TestKit
|
||||
// A duplicate/late failure from gen-1 must be ignored (no third dial).
|
||||
first.OnError(new InvalidOperationException("late duplicate"));
|
||||
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(400));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken);
|
||||
dial.Invocations.Count.ShouldBe(2);
|
||||
}
|
||||
|
||||
@@ -215,23 +215,23 @@ public sealed class TelemetryDialSupervisorTests : TestKit
|
||||
|
||||
// First node connects → both broadcasters flip to connected.
|
||||
node1.OnMapped(Alarm("a1"));
|
||||
AwaitAssert(() => sinks.PillStates.ToArray().ShouldBe(new[] { true }), Timeout);
|
||||
AwaitAssert(() => sinks.PillStates.ToArray().ShouldBe(new[] { true }), Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Second node connects → still connected, no extra transition.
|
||||
node2.OnMapped(Alarm("a2"));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken);
|
||||
sinks.PillStates.ToArray().ShouldBe(new[] { true });
|
||||
|
||||
// node-1 drops but node-2 is still up → pill stays true.
|
||||
node1.OnError(new InvalidOperationException("down"));
|
||||
node1.Complete();
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken);
|
||||
sinks.PillStates.ToArray().ShouldBe(new[] { true });
|
||||
|
||||
// node-2 drops too → all down → pill flips false.
|
||||
node2.OnError(new InvalidOperationException("down"));
|
||||
node2.Complete();
|
||||
AwaitAssert(() => sinks.PillStates.ToArray().ShouldBe(new[] { true, false }), Timeout);
|
||||
AwaitAssert(() => sinks.PillStates.ToArray().ShouldBe(new[] { true, false }), Timeout, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -250,7 +250,7 @@ public sealed class TelemetryDialSupervisorTests : TestKit
|
||||
|
||||
Sys.Stop(actor);
|
||||
|
||||
AwaitCondition(() => inv.All(i => i.Ct.IsCancellationRequested), Timeout, PollInterval);
|
||||
AwaitCondition(() => inv.All(i => i.Ct.IsCancellationRequested), Timeout, PollInterval, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -264,7 +264,7 @@ public sealed class TelemetryDialSupervisorTests : TestKit
|
||||
Node("null-port", "h2", grpcPort: null, enabled: true, maintenance: false),
|
||||
Node("maintenance", "h3", grpcPort: 5300, enabled: true, maintenance: true),
|
||||
Node("disabled", "h4", grpcPort: 5300, enabled: false, maintenance: false));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var source = TelemetryNodeSource.Create(factory, NullLogger.Instance);
|
||||
|
||||
@@ -96,7 +96,7 @@ public sealed class EffectiveNameGuardTests
|
||||
var guard = Guard(SeedFactory());
|
||||
|
||||
var result = await guard.CheckAsync(
|
||||
EquipmentId, "brand-new-name", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null);
|
||||
EquipmentId, "brand-new-name", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public sealed class EffectiveNameGuardTests
|
||||
|
||||
// A new VirtualTag proposing "speed" collides with reference REF-1's effective name.
|
||||
var result = await guard.CheckAsync(
|
||||
EquipmentId, "speed", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null);
|
||||
EquipmentId, "speed", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.ShouldBe($"effective name 'speed' already used by reference '{RefNoOverrideId}' in equipment '{EquipmentId}'");
|
||||
@@ -121,7 +121,7 @@ public sealed class EffectiveNameGuardTests
|
||||
|
||||
// A new reference proposing "temperature" collides with REF-2's OVERRIDE effective name.
|
||||
var result = await guard.CheckAsync(
|
||||
EquipmentId, "temperature", EffectiveNameSourceKind.Reference, excludeSourceId: null);
|
||||
EquipmentId, "temperature", EffectiveNameSourceKind.Reference, excludeSourceId: null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.ShouldBe($"effective name 'temperature' already used by reference '{RefWithOverrideId}' in equipment '{EquipmentId}'");
|
||||
@@ -133,7 +133,7 @@ public sealed class EffectiveNameGuardTests
|
||||
var guard = Guard(SeedFactory());
|
||||
|
||||
var result = await guard.CheckAsync(
|
||||
EquipmentId, "highpress", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null);
|
||||
EquipmentId, "highpress", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.ShouldBe($"effective name 'highpress' already used by ScriptedAlarm '{ScriptedAlarmId}' in equipment '{EquipmentId}'");
|
||||
@@ -147,7 +147,7 @@ public sealed class EffectiveNameGuardTests
|
||||
// Editing REF-2 to change its override to "temperature" (its own current effective name)
|
||||
// must NOT collide with itself.
|
||||
var result = await guard.CheckAsync(
|
||||
EquipmentId, "temperature", EffectiveNameSourceKind.Reference, excludeSourceId: RefWithOverrideId);
|
||||
EquipmentId, "temperature", EffectiveNameSourceKind.Reference, excludeSourceId: RefWithOverrideId, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
@@ -160,7 +160,7 @@ public sealed class EffectiveNameGuardTests
|
||||
// Excluding a VirtualTag whose id happens to equal a reference id must NOT excuse the
|
||||
// reference collision — the self-row is identified by (kind, id) together.
|
||||
var result = await guard.CheckAsync(
|
||||
EquipmentId, "speed", EffectiveNameSourceKind.VirtualTag, excludeSourceId: RefNoOverrideId);
|
||||
EquipmentId, "speed", EffectiveNameSourceKind.VirtualTag, excludeSourceId: RefNoOverrideId, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldNotBeNull();
|
||||
result.ShouldBe($"effective name 'speed' already used by reference '{RefNoOverrideId}' in equipment '{EquipmentId}'");
|
||||
@@ -173,7 +173,7 @@ public sealed class EffectiveNameGuardTests
|
||||
|
||||
// "Speed" (capital S) must NOT collide with the existing "speed" (ordinal comparison).
|
||||
var result = await guard.CheckAsync(
|
||||
EquipmentId, "Speed", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null);
|
||||
EquipmentId, "Speed", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
@@ -185,7 +185,7 @@ public sealed class EffectiveNameGuardTests
|
||||
|
||||
// Same name, different equipment → no collision (per-equipment NodeId space).
|
||||
var result = await guard.CheckAsync(
|
||||
"EQ-000000000002", "speed", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null);
|
||||
"EQ-000000000002", "speed", EffectiveNameSourceKind.VirtualTag, excludeSourceId: null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
@@ -210,10 +210,10 @@ public sealed class EffectiveNameGuardTests
|
||||
var guard = Guard(factory);
|
||||
|
||||
var expected = await guard.CheckAsync(
|
||||
EquipmentId, expectedCollidingName, EffectiveNameSourceKind.Reference, excludeSourceId: null);
|
||||
EquipmentId, expectedCollidingName, EffectiveNameSourceKind.Reference, excludeSourceId: null, TestContext.Current.CancellationToken);
|
||||
expected.ShouldNotBeNull();
|
||||
|
||||
var actual = await guard.CheckManyAsync(EquipmentId, names, EffectiveNameSourceKind.Reference);
|
||||
var actual = await guard.CheckManyAsync(EquipmentId, names, EffectiveNameSourceKind.Reference, TestContext.Current.CancellationToken);
|
||||
|
||||
actual.ShouldBe(expected);
|
||||
}
|
||||
@@ -227,7 +227,7 @@ public sealed class EffectiveNameGuardTests
|
||||
var result = await guard.CheckManyAsync(
|
||||
EquipmentId,
|
||||
["free-a", "free-b", "Speed" /* ordinal: capital S does not collide */, "free-c"],
|
||||
EffectiveNameSourceKind.Reference);
|
||||
EffectiveNameSourceKind.Reference, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
@@ -238,7 +238,7 @@ public sealed class EffectiveNameGuardTests
|
||||
{
|
||||
var guard = Guard(SeedFactory());
|
||||
|
||||
var result = await guard.CheckManyAsync(EquipmentId, [], EffectiveNameSourceKind.Reference);
|
||||
var result = await guard.CheckManyAsync(EquipmentId, [], EffectiveNameSourceKind.Reference, TestContext.Current.CancellationToken);
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ public sealed class RawTagConfigGateTests
|
||||
new RawTagImportRow(null, Tag("good2", GoodS7)),
|
||||
};
|
||||
|
||||
var result = await service.ImportTagsAsync(RawTreeTestDb.RootDeviceId, rows);
|
||||
var result = await service.ImportTagsAsync(RawTreeTestDb.RootDeviceId, rows, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Inserted.ShouldBe(0);
|
||||
result.Errors.ShouldContain(e => e.Contains("Row 2") && e.Contains("TagConfig"));
|
||||
@@ -256,7 +256,7 @@ public sealed class RawTagConfigGateTests
|
||||
|
||||
var result = await service.ImportTagsAsync(
|
||||
RawTreeTestDb.RootDeviceId,
|
||||
new[] { new RawTagImportRow(null, Tag("ok1", GoodS7)), new RawTagImportRow(null, Tag("ok2", GoodS7)) });
|
||||
new[] { new RawTagImportRow(null, Tag("ok1", GoodS7)), new RawTagImportRow(null, Tag("ok2", GoodS7)) }, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Errors.ShouldBeEmpty();
|
||||
result.Inserted.ShouldBe(2);
|
||||
@@ -273,7 +273,7 @@ public sealed class RawTagConfigGateTests
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var bad = await service.CreateTagAsync(
|
||||
RawTreeTestDb.RootDeviceId, null, Tag("newTag", """{"address":"DB1.DBW0"}"""));
|
||||
RawTreeTestDb.RootDeviceId, null, Tag("newTag", """{"address":"DB1.DBW0"}"""), TestContext.Current.CancellationToken);
|
||||
|
||||
bad.Ok.ShouldBeFalse();
|
||||
bad.Error.ShouldNotBeNull();
|
||||
@@ -285,7 +285,7 @@ public sealed class RawTagConfigGateTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
(await service.CreateTagAsync(RawTreeTestDb.RootDeviceId, null, Tag("newTag", GoodS7)))
|
||||
(await service.CreateTagAsync(RawTreeTestDb.RootDeviceId, null, Tag("newTag", GoodS7), TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ public sealed class RawTagConfigGateTests
|
||||
}
|
||||
|
||||
var bad = await service.UpdateTagAsync(
|
||||
RawTreeTestDb.ReferencedTagId, Tag("temperature", """{"address":"DB1.DBW0"}"""), rowVersion);
|
||||
RawTreeTestDb.ReferencedTagId, Tag("temperature", """{"address":"DB1.DBW0"}"""), rowVersion, TestContext.Current.CancellationToken);
|
||||
|
||||
bad.Ok.ShouldBeFalse();
|
||||
bad.Error.ShouldNotBeNull();
|
||||
|
||||
+6
-6
@@ -75,7 +75,7 @@ public sealed class RawTreeServiceRenameWarningTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = DeviceRowVersion(dbName, DevAllId);
|
||||
|
||||
var result = await service.RenameDeviceAsync(DevAllId, "Dev-renamed", rv);
|
||||
var result = await service.RenameDeviceAsync(DevAllId, "Dev-renamed", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Warnings.ShouldContain(w => w.Contains("historian")); // (a)
|
||||
@@ -90,7 +90,7 @@ public sealed class RawTreeServiceRenameWarningTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = DeviceRowVersion(dbName, DevPinnedId);
|
||||
|
||||
var result = await service.RenameDeviceAsync(DevPinnedId, "Dev-pinned2", rv);
|
||||
var result = await service.RenameDeviceAsync(DevPinnedId, "Dev-pinned2", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
// A historianTagname override pins the tagname, so its history does NOT fork — no (a) warning; and
|
||||
@@ -105,7 +105,7 @@ public sealed class RawTreeServiceRenameWarningTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = DeviceRowVersion(dbName, DevPlainId);
|
||||
|
||||
var result = await service.RenameDeviceAsync(DevPlainId, "Dev-plain2", rv);
|
||||
var result = await service.RenameDeviceAsync(DevPlainId, "Dev-plain2", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Warnings.ShouldBeEmpty();
|
||||
@@ -119,7 +119,7 @@ public sealed class RawTreeServiceRenameWarningTests
|
||||
|
||||
// Renaming the DRIVER (an ancestor of the scripted tag) still computes the tag's unchanged OLD
|
||||
// RawPath (rename not yet saved) and finds it as a substring of the script body.
|
||||
var result = await service.RenameDriverAsync(DriverId, "modbus-renamed", rv);
|
||||
var result = await service.RenameDriverAsync(DriverId, "modbus-renamed", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Warnings.ShouldContain(w => w.Contains("script") && w.Contains(ScriptName));
|
||||
@@ -146,7 +146,7 @@ public sealed class RawTreeServiceRenameWarningTests
|
||||
});
|
||||
var rv = DeviceRowVersion(dbName, DevAllId);
|
||||
|
||||
var result = await service.RenameDeviceAsync(DevAllId, "Dev-renamed", rv);
|
||||
var result = await service.RenameDeviceAsync(DevAllId, "Dev-renamed", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
var scriptWarnings = result.Warnings.Where(w => w.Contains("script")).ToList();
|
||||
@@ -194,7 +194,7 @@ public sealed class RawTreeServiceRenameWarningTests
|
||||
});
|
||||
var rv = DeviceRowVersion(dbName, "DEV-bulk");
|
||||
|
||||
var result = await service.RenameDeviceAsync("DEV-bulk", "Dev-bulk2", rv);
|
||||
var result = await service.RenameDeviceAsync("DEV-bulk", "Dev-bulk2", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
// Ordinal sort: 'S-first' < 'S-last' < 'S-middle'.
|
||||
|
||||
+6
-6
@@ -47,11 +47,11 @@ public sealed class RawTreeServiceSecretRefRoundTripTests
|
||||
"{\"Password\":\"secret:opcua/x/pw\",\"UserCertificatePassword\":\"secret:opcua/x/certpw\"}";
|
||||
|
||||
var result = await service.UpdateDriverAsync(
|
||||
RawTreeTestDb.FolderedDriverId, "modbus-1", config, null, rv);
|
||||
RawTreeTestDb.FolderedDriverId, "modbus-1", config, null, rv, TestContext.Current.CancellationToken);
|
||||
result.Ok.ShouldBeTrue();
|
||||
|
||||
// Reload through the same seam the modal uses.
|
||||
var reloaded = await service.LoadDriverForEditAsync(RawTreeTestDb.FolderedDriverId);
|
||||
var reloaded = await service.LoadDriverForEditAsync(RawTreeTestDb.FolderedDriverId, TestContext.Current.CancellationToken);
|
||||
reloaded.ShouldNotBeNull();
|
||||
|
||||
// Byte-identical: nothing on the save path rewrote the string.
|
||||
@@ -74,11 +74,11 @@ public sealed class RawTreeServiceSecretRefRoundTripTests
|
||||
const string config = "{\"Gateway\":{\"ApiKeySecretRef\":\"secret:galaxy/x/apikey\"}}";
|
||||
|
||||
var created = await service.CreateDriverAsync(
|
||||
RawTreeTestDb.ClusterId, RawTreeTestDb.RootFolderId, "galaxy-1", "Galaxy", config);
|
||||
RawTreeTestDb.ClusterId, RawTreeTestDb.RootFolderId, "galaxy-1", "Galaxy", config, TestContext.Current.CancellationToken);
|
||||
created.Ok.ShouldBeTrue();
|
||||
created.CreatedId.ShouldNotBeNull();
|
||||
|
||||
var reloaded = await service.LoadDriverForEditAsync(created.CreatedId!);
|
||||
var reloaded = await service.LoadDriverForEditAsync(created.CreatedId!, TestContext.Current.CancellationToken);
|
||||
reloaded.ShouldNotBeNull();
|
||||
|
||||
reloaded!.DriverConfig.ShouldBe(config);
|
||||
@@ -99,10 +99,10 @@ public sealed class RawTreeServiceSecretRefRoundTripTests
|
||||
"{\"Password\":\"env:OPCUA_PW\",\"UserCertificatePassword\":\"file:/run/secrets/certpw\"}";
|
||||
|
||||
var result = await service.UpdateDriverAsync(
|
||||
RawTreeTestDb.FolderedDriverId, "modbus-1", config, null, rv);
|
||||
RawTreeTestDb.FolderedDriverId, "modbus-1", config, null, rv, TestContext.Current.CancellationToken);
|
||||
result.Ok.ShouldBeTrue();
|
||||
|
||||
var reloaded = await service.LoadDriverForEditAsync(RawTreeTestDb.FolderedDriverId);
|
||||
var reloaded = await service.LoadDriverForEditAsync(RawTreeTestDb.FolderedDriverId, TestContext.Current.CancellationToken);
|
||||
reloaded!.DriverConfig.ShouldBe(config);
|
||||
reloaded.DriverConfig.ShouldContain("env:OPCUA_PW");
|
||||
reloaded.DriverConfig.ShouldContain("file:/run/secrets/certpw");
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class RawTreeServiceTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var roots = await service.LoadRootsAsync();
|
||||
var roots = await service.LoadRootsAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var ent = roots.ShouldHaveSingleItem();
|
||||
ent.Kind.ShouldBe(RawNodeKind.Enterprise);
|
||||
@@ -57,9 +57,9 @@ public sealed class RawTreeServiceTests
|
||||
public async Task LoadChildren_of_cluster_yields_root_folders_and_root_drivers()
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
var cluster = (await service.LoadRootsAsync()).Single().Children.Single();
|
||||
var cluster = (await service.LoadRootsAsync(TestContext.Current.CancellationToken)).Single().Children.Single();
|
||||
|
||||
var children = await service.LoadChildrenAsync(cluster);
|
||||
var children = await service.LoadChildrenAsync(cluster, TestContext.Current.CancellationToken);
|
||||
|
||||
var folder = children.Single(n => n.Kind == RawNodeKind.Folder);
|
||||
folder.DisplayName.ShouldBe("PLCs");
|
||||
@@ -77,29 +77,29 @@ public sealed class RawTreeServiceTests
|
||||
public async Task LoadChildren_propagates_driver_type_down_to_device_group_and_tag()
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
var cluster = (await service.LoadRootsAsync()).Single().Children.Single();
|
||||
var folder = (await service.LoadChildrenAsync(cluster)).Single(n => n.Kind == RawNodeKind.Folder);
|
||||
var cluster = (await service.LoadRootsAsync(TestContext.Current.CancellationToken)).Single().Children.Single();
|
||||
var folder = (await service.LoadChildrenAsync(cluster, TestContext.Current.CancellationToken)).Single(n => n.Kind == RawNodeKind.Folder);
|
||||
|
||||
var driver = (await service.LoadChildrenAsync(folder)).ShouldHaveSingleItem();
|
||||
var driver = (await service.LoadChildrenAsync(folder, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
driver.Kind.ShouldBe(RawNodeKind.Driver);
|
||||
driver.DriverType.ShouldBe("Modbus");
|
||||
|
||||
var device = (await service.LoadChildrenAsync(driver)).ShouldHaveSingleItem();
|
||||
var device = (await service.LoadChildrenAsync(driver, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
device.Kind.ShouldBe(RawNodeKind.Device);
|
||||
device.DriverType.ShouldBe("Modbus"); // propagated
|
||||
device.ChildCount.ShouldBe(1); // one root group (tags live under the group)
|
||||
|
||||
var group = (await service.LoadChildrenAsync(device)).ShouldHaveSingleItem();
|
||||
var group = (await service.LoadChildrenAsync(device, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
group.Kind.ShouldBe(RawNodeKind.TagGroup);
|
||||
group.DriverType.ShouldBe("Modbus"); // propagated
|
||||
group.ChildCount.ShouldBe(2); // two tags
|
||||
|
||||
var tags = await service.LoadChildrenAsync(group);
|
||||
var tags = await service.LoadChildrenAsync(group, TestContext.Current.CancellationToken);
|
||||
tags.Count.ShouldBe(2);
|
||||
tags.ShouldAllBe(t => t.Kind == RawNodeKind.Tag);
|
||||
tags.ShouldAllBe(t => !t.HasLazyChildren);
|
||||
tags.ShouldAllBe(t => t.DriverType == "Modbus"); // propagated
|
||||
(await service.LoadChildrenAsync(tags[0])).ShouldBeEmpty(); // leaf
|
||||
(await service.LoadChildrenAsync(tags[0], TestContext.Current.CancellationToken)).ShouldBeEmpty(); // leaf
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,24 +112,24 @@ public sealed class RawTreeServiceTests
|
||||
public async Task LoadChildren_yields_tag_rows_carrying_id_name_and_row_version()
|
||||
{
|
||||
var (service, dbName) = Seeded();
|
||||
var cluster = (await service.LoadRootsAsync()).Single().Children.Single();
|
||||
var cluster = (await service.LoadRootsAsync(TestContext.Current.CancellationToken)).Single().Children.Single();
|
||||
|
||||
// Group-child loader: two tags under "Motors".
|
||||
var folder = (await service.LoadChildrenAsync(cluster)).Single(n => n.Kind == RawNodeKind.Folder);
|
||||
var driver = (await service.LoadChildrenAsync(folder)).ShouldHaveSingleItem();
|
||||
var device = (await service.LoadChildrenAsync(driver)).ShouldHaveSingleItem();
|
||||
var group = (await service.LoadChildrenAsync(device)).ShouldHaveSingleItem();
|
||||
var folder = (await service.LoadChildrenAsync(cluster, TestContext.Current.CancellationToken)).Single(n => n.Kind == RawNodeKind.Folder);
|
||||
var driver = (await service.LoadChildrenAsync(folder, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
var device = (await service.LoadChildrenAsync(driver, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
var group = (await service.LoadChildrenAsync(device, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
|
||||
var plain = (await service.LoadChildrenAsync(group)).Single(t => t.EntityId == RawTreeTestDb.PlainTagId);
|
||||
var plain = (await service.LoadChildrenAsync(group, TestContext.Current.CancellationToken)).Single(t => t.EntityId == RawTreeTestDb.PlainTagId);
|
||||
plain.DisplayName.ShouldBe("state");
|
||||
plain.RowVersion.ShouldBe(
|
||||
RowVersionOf(dbName, db => db.Tags.Single(t => t.TagId == RawTreeTestDb.PlainTagId).RowVersion));
|
||||
|
||||
// Device-child loader: the cluster-root device's own tag.
|
||||
var rootDriver = (await service.LoadChildrenAsync(cluster)).Single(n => n.Kind == RawNodeKind.Driver);
|
||||
var rootDevice = (await service.LoadChildrenAsync(rootDriver)).ShouldHaveSingleItem();
|
||||
var rootDriver = (await service.LoadChildrenAsync(cluster, TestContext.Current.CancellationToken)).Single(n => n.Kind == RawNodeKind.Driver);
|
||||
var rootDevice = (await service.LoadChildrenAsync(rootDriver, TestContext.Current.CancellationToken)).ShouldHaveSingleItem();
|
||||
|
||||
var referenced = (await service.LoadChildrenAsync(rootDevice))
|
||||
var referenced = (await service.LoadChildrenAsync(rootDevice, TestContext.Current.CancellationToken))
|
||||
.Single(t => t.EntityId == RawTreeTestDb.ReferencedTagId);
|
||||
referenced.Kind.ShouldBe(RawNodeKind.Tag);
|
||||
referenced.DisplayName.ShouldBe("temperature");
|
||||
@@ -144,11 +144,11 @@ public sealed class RawTreeServiceTests
|
||||
{
|
||||
var (service, dbName) = Seeded();
|
||||
|
||||
var folder = await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "Cell-B");
|
||||
var folder = await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "Cell-B", TestContext.Current.CancellationToken);
|
||||
folder.Ok.ShouldBeTrue();
|
||||
folder.CreatedId.ShouldNotBeNull();
|
||||
|
||||
var driver = await service.CreateDriverAsync(RawTreeTestDb.ClusterId, folder.CreatedId, "abcip-1", "AbCip", "{}");
|
||||
var driver = await service.CreateDriverAsync(RawTreeTestDb.ClusterId, folder.CreatedId, "abcip-1", "AbCip", "{}", TestContext.Current.CancellationToken);
|
||||
driver.Ok.ShouldBeTrue();
|
||||
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -160,7 +160,7 @@ public sealed class RawTreeServiceTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dup = await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "PLCs"); // already a root folder
|
||||
var dup = await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "PLCs", TestContext.Current.CancellationToken); // already a root folder
|
||||
dup.Ok.ShouldBeFalse();
|
||||
dup.Error!.ShouldContain("already exists");
|
||||
}
|
||||
@@ -170,7 +170,7 @@ public sealed class RawTreeServiceTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var bad = await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "a/b");
|
||||
var bad = await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "a/b", TestContext.Current.CancellationToken);
|
||||
bad.Ok.ShouldBeFalse();
|
||||
bad.Error!.ShouldContain("/");
|
||||
}
|
||||
@@ -180,7 +180,7 @@ public sealed class RawTreeServiceTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dup = await service.CreateTagGroupAsync(RawTreeTestDb.FolderedDeviceId, null, "Motors");
|
||||
var dup = await service.CreateTagGroupAsync(RawTreeTestDb.FolderedDeviceId, null, "Motors", TestContext.Current.CancellationToken);
|
||||
dup.Ok.ShouldBeFalse();
|
||||
dup.Error!.ShouldContain("already exists");
|
||||
}
|
||||
@@ -193,7 +193,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.RawFolders.Single(f => f.RawFolderId == RawTreeTestDb.RootFolderId).RowVersion);
|
||||
|
||||
var result = await service.RenameFolderAsync(RawTreeTestDb.RootFolderId, "PLC", rv);
|
||||
var result = await service.RenameFolderAsync(RawTreeTestDb.RootFolderId, "PLC", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Warnings.ShouldContain(w => w.Contains("historized"));
|
||||
@@ -205,7 +205,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.DriverInstances.Single(d => d.DriverInstanceId == RawTreeTestDb.RootDriverId).RowVersion);
|
||||
|
||||
var result = await service.RenameDriverAsync(RawTreeTestDb.RootDriverId, "s7-main", rv);
|
||||
var result = await service.RenameDriverAsync(RawTreeTestDb.RootDriverId, "s7-main", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Warnings.ShouldContain(w => w.Contains(RawTreeTestDb.EquipmentName));
|
||||
@@ -215,10 +215,10 @@ public sealed class RawTreeServiceTests
|
||||
public async Task RenameFolder_rejects_sibling_collision()
|
||||
{
|
||||
var (service, dbName) = Seeded();
|
||||
await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "Spare");
|
||||
await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "Spare", TestContext.Current.CancellationToken);
|
||||
var rv = RowVersionOf(dbName, db => db.RawFolders.Single(f => f.RawFolderId == RawTreeTestDb.RootFolderId).RowVersion);
|
||||
|
||||
var result = await service.RenameFolderAsync(RawTreeTestDb.RootFolderId, "Spare", rv);
|
||||
var result = await service.RenameFolderAsync(RawTreeTestDb.RootFolderId, "Spare", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error!.ShouldContain("already exists");
|
||||
@@ -232,7 +232,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.RawFolders.Single(f => f.RawFolderId == RawTreeTestDb.RootFolderId).RowVersion);
|
||||
|
||||
var result = await service.DeleteFolderAsync(RawTreeTestDb.RootFolderId, rv);
|
||||
var result = await service.DeleteFolderAsync(RawTreeTestDb.RootFolderId, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error!.ShouldContain("driver");
|
||||
@@ -245,7 +245,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.Devices.Single(d => d.DeviceId == RawTreeTestDb.RootDeviceId).RowVersion);
|
||||
|
||||
var result = await service.DeleteDeviceAsync(RawTreeTestDb.RootDeviceId, rv);
|
||||
var result = await service.DeleteDeviceAsync(RawTreeTestDb.RootDeviceId, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error!.ShouldContain("tags");
|
||||
@@ -257,7 +257,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.TagGroups.Single(g => g.TagGroupId == RawTreeTestDb.RootGroupId).RowVersion);
|
||||
|
||||
var result = await service.DeleteTagGroupAsync(RawTreeTestDb.RootGroupId, rv);
|
||||
var result = await service.DeleteTagGroupAsync(RawTreeTestDb.RootGroupId, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error!.ShouldContain("tags");
|
||||
@@ -269,7 +269,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.DriverInstances.Single(d => d.DriverInstanceId == RawTreeTestDb.RootDriverId).RowVersion);
|
||||
|
||||
var result = await service.DeleteDriverAsync(RawTreeTestDb.RootDriverId, rv);
|
||||
var result = await service.DeleteDriverAsync(RawTreeTestDb.RootDriverId, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error!.ShouldContain("Device1"); // the offending device is named
|
||||
@@ -281,7 +281,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.Tags.Single(t => t.TagId == RawTreeTestDb.ReferencedTagId).RowVersion);
|
||||
|
||||
var result = await service.DeleteTagAsync(RawTreeTestDb.ReferencedTagId, rv);
|
||||
var result = await service.DeleteTagAsync(RawTreeTestDb.ReferencedTagId, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error!.ShouldContain(RawTreeTestDb.EquipmentName);
|
||||
@@ -296,7 +296,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.Tags.Single(t => t.TagId == RawTreeTestDb.PlainTagId).RowVersion);
|
||||
|
||||
var result = await service.DeleteTagAsync(RawTreeTestDb.PlainTagId, rv);
|
||||
var result = await service.DeleteTagAsync(RawTreeTestDb.PlainTagId, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -307,10 +307,10 @@ public sealed class RawTreeServiceTests
|
||||
public async Task DeleteFolder_succeeds_when_empty()
|
||||
{
|
||||
var (service, dbName) = Seeded();
|
||||
var created = await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "Empty");
|
||||
var created = await service.CreateFolderAsync(RawTreeTestDb.ClusterId, null, "Empty", TestContext.Current.CancellationToken);
|
||||
var rv = RowVersionOf(dbName, db => db.RawFolders.Single(f => f.RawFolderId == created.CreatedId!).RowVersion);
|
||||
|
||||
var result = await service.DeleteFolderAsync(created.CreatedId!, rv);
|
||||
var result = await service.DeleteFolderAsync(created.CreatedId!, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
}
|
||||
@@ -323,7 +323,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.DriverInstances.Single(d => d.DriverInstanceId == RawTreeTestDb.RootDriverId).RowVersion);
|
||||
|
||||
var result = await service.MoveDriverToFolderAsync(RawTreeTestDb.RootDriverId, RawTreeTestDb.RootFolderId, rv);
|
||||
var result = await service.MoveDriverToFolderAsync(RawTreeTestDb.RootDriverId, RawTreeTestDb.RootFolderId, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -337,7 +337,7 @@ public sealed class RawTreeServiceTests
|
||||
var (service, dbName) = Seeded();
|
||||
var rv = RowVersionOf(dbName, db => db.DriverInstances.Single(d => d.DriverInstanceId == RawTreeTestDb.FolderedDriverId).RowVersion);
|
||||
|
||||
var result = await service.MoveDriverToFolderAsync(RawTreeTestDb.FolderedDriverId, null, rv);
|
||||
var result = await service.MoveDriverToFolderAsync(RawTreeTestDb.FolderedDriverId, null, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -360,7 +360,7 @@ public sealed class RawTreeServiceTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.RenameFolderAsync(RawTreeTestDb.RootFolderId, "Whatever", rv);
|
||||
var result = await service.RenameFolderAsync(RawTreeTestDb.RootFolderId, "Whatever", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -372,7 +372,7 @@ public sealed class RawTreeServiceTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dto = await service.LoadTagForEditAsync(RawTreeTestDb.HistorizedTagId);
|
||||
var dto = await service.LoadTagForEditAsync(RawTreeTestDb.HistorizedTagId, TestContext.Current.CancellationToken);
|
||||
|
||||
dto.ShouldNotBeNull();
|
||||
dto.Value.Name.ShouldBe("speed");
|
||||
|
||||
@@ -62,7 +62,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
var (service, dbName) = Seeded();
|
||||
|
||||
var result = await service.CreateTagAsync(
|
||||
RawTreeTestDb.FolderedDeviceId, RawTreeTestDb.RootGroupId, Tag("torque"));
|
||||
RawTreeTestDb.FolderedDeviceId, RawTreeTestDb.RootGroupId, Tag("torque"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.CreatedId.ShouldNotBeNull();
|
||||
@@ -81,7 +81,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
|
||||
var result = await service.CreateTagAsync(
|
||||
RawTreeTestDb.FolderedDeviceId, null,
|
||||
new RawTagInput("flow", "Float", TagAccessLevel.Read, false, " ", ModbusConfig));
|
||||
new RawTagInput("flow", "Float", TagAccessLevel.Read, false, " ", ModbusConfig), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -94,7 +94,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
var (service, _) = Seeded();
|
||||
|
||||
// "speed" already exists under RootGroup.
|
||||
var dup = await service.CreateTagAsync(RawTreeTestDb.FolderedDeviceId, RawTreeTestDb.RootGroupId, Tag("speed"));
|
||||
var dup = await service.CreateTagAsync(RawTreeTestDb.FolderedDeviceId, RawTreeTestDb.RootGroupId, Tag("speed"), TestContext.Current.CancellationToken);
|
||||
|
||||
dup.Ok.ShouldBeFalse();
|
||||
dup.Error!.ShouldContain("already exists");
|
||||
@@ -106,7 +106,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var bad = await service.CreateTagAsync(
|
||||
RawTreeTestDb.FolderedDeviceId, null, Tag("pressure", tagConfig: "{not json"));
|
||||
RawTreeTestDb.FolderedDeviceId, null, Tag("pressure", tagConfig: "{not json"), TestContext.Current.CancellationToken);
|
||||
|
||||
bad.Ok.ShouldBeFalse();
|
||||
bad.Error!.ShouldContain("JSON");
|
||||
@@ -117,7 +117,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var bad = await service.CreateTagAsync(RawTreeTestDb.FolderedDeviceId, null, Tag("a/b"));
|
||||
var bad = await service.CreateTagAsync(RawTreeTestDb.FolderedDeviceId, null, Tag("a/b"), TestContext.Current.CancellationToken);
|
||||
|
||||
bad.Ok.ShouldBeFalse();
|
||||
bad.Error!.ShouldContain("/");
|
||||
@@ -139,7 +139,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
new RawTagInput(
|
||||
"running", "Boolean", TagAccessLevel.ReadWrite, true, "pg1",
|
||||
"""{"region":"HoldingRegisters","address":1,"scale":2}"""),
|
||||
rv);
|
||||
rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -161,7 +161,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
// Rename "state" (PlainTag) to "speed" — a sibling under the same group ⇒ collision.
|
||||
var rv = RowVersionOf(dbName, db => db.Tags.Single(t => t.TagId == RawTreeTestDb.PlainTagId).RowVersion);
|
||||
|
||||
var result = await service.UpdateTagAsync(RawTreeTestDb.PlainTagId, Tag("speed"), rv);
|
||||
var result = await service.UpdateTagAsync(RawTreeTestDb.PlainTagId, Tag("speed"), rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error!.ShouldContain("already exists");
|
||||
@@ -174,7 +174,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
var rv = RowVersionOf(dbName, db => db.Tags.Single(t => t.TagId == RawTreeTestDb.PlainTagId).RowVersion);
|
||||
|
||||
// Keeping its own name "state" must not trip the excl-self uniqueness check.
|
||||
var result = await service.UpdateTagAsync(RawTreeTestDb.PlainTagId, Tag("state", "Boolean"), rv);
|
||||
var result = await service.UpdateTagAsync(RawTreeTestDb.PlainTagId, Tag("state", "Boolean"), rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
}
|
||||
@@ -191,7 +191,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.UpdateTagAsync(RawTreeTestDb.PlainTagId, Tag("whatever", "Boolean"), rv);
|
||||
var result = await service.UpdateTagAsync(RawTreeTestDb.PlainTagId, Tag("whatever", "Boolean"), rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -211,7 +211,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
new RawTagImportRow("Line/B", S7Tag("t3")), // reuses Line, new B
|
||||
};
|
||||
|
||||
var result = await service.ImportTagsAsync(RawTreeTestDb.RootDeviceId, rows);
|
||||
var result = await service.ImportTagsAsync(RawTreeTestDb.RootDeviceId, rows, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Errors.ShouldBeEmpty();
|
||||
result.Inserted.ShouldBe(3);
|
||||
@@ -232,7 +232,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
var (service, dbName) = Seeded();
|
||||
|
||||
var result = await service.ImportTagsAsync(
|
||||
RawTreeTestDb.RootDeviceId, new[] { new RawTagImportRow(null, S7Tag("rootTag")) });
|
||||
RawTreeTestDb.RootDeviceId, new[] { new RawTagImportRow(null, S7Tag("rootTag")) }, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Inserted.ShouldBe(1);
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -251,7 +251,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
new RawTagImportRow("Grp", S7Tag("good2")),
|
||||
};
|
||||
|
||||
var result = await service.ImportTagsAsync(RawTreeTestDb.RootDeviceId, rows);
|
||||
var result = await service.ImportTagsAsync(RawTreeTestDb.RootDeviceId, rows, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Inserted.ShouldBe(0);
|
||||
result.Errors.ShouldNotBeEmpty();
|
||||
@@ -274,7 +274,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
new RawTagImportRow("Grp", S7Tag("dup")), // same path + name within the batch
|
||||
};
|
||||
|
||||
var result = await service.ImportTagsAsync(RawTreeTestDb.RootDeviceId, rows);
|
||||
var result = await service.ImportTagsAsync(RawTreeTestDb.RootDeviceId, rows, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Inserted.ShouldBe(0);
|
||||
result.Errors.ShouldContain(e => e.Contains("Row 2") && e.Contains("already exists"));
|
||||
@@ -287,7 +287,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
|
||||
// "temperature" already sits at the device root of RootDevice.
|
||||
var result = await service.ImportTagsAsync(
|
||||
RawTreeTestDb.RootDeviceId, new[] { new RawTagImportRow(null, S7Tag("temperature")) });
|
||||
RawTreeTestDb.RootDeviceId, new[] { new RawTagImportRow(null, S7Tag("temperature")) }, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Inserted.ShouldBe(0);
|
||||
result.Errors.ShouldContain(e => e.Contains("already exists"));
|
||||
@@ -300,7 +300,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dto = await service.LoadDriverForEditAsync(RawTreeTestDb.FolderedDriverId);
|
||||
var dto = await service.LoadDriverForEditAsync(RawTreeTestDb.FolderedDriverId, TestContext.Current.CancellationToken);
|
||||
|
||||
dto.ShouldNotBeNull();
|
||||
dto!.Name.ShouldBe("modbus-1");
|
||||
@@ -312,7 +312,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
public async Task LoadDriverForEdit_returns_null_when_missing()
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
(await service.LoadDriverForEditAsync("DRV-nope")).ShouldBeNull();
|
||||
(await service.LoadDriverForEditAsync("DRV-nope", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -322,7 +322,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
var rv = RowVersionOf(dbName, db => db.DriverInstances.Single(d => d.DriverInstanceId == RawTreeTestDb.FolderedDriverId).RowVersion);
|
||||
|
||||
var result = await service.UpdateDriverAsync(
|
||||
RawTreeTestDb.FolderedDriverId, "modbus-main", "{\"Host\":\"10.0.0.1\"}", "{\"x\":1}", rv);
|
||||
RawTreeTestDb.FolderedDriverId, "modbus-main", "{\"Host\":\"10.0.0.1\"}", "{\"x\":1}", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -338,10 +338,10 @@ public sealed class RawTreeServiceWaveBTests
|
||||
{
|
||||
var (service, dbName) = Seeded();
|
||||
// Two cluster-root vs foldered drivers differ in scope; create a sibling in the same folder to collide.
|
||||
await service.CreateDriverAsync(RawTreeTestDb.ClusterId, RawTreeTestDb.RootFolderId, "modbus-2", "Modbus", "{}");
|
||||
await service.CreateDriverAsync(RawTreeTestDb.ClusterId, RawTreeTestDb.RootFolderId, "modbus-2", "Modbus", "{}", TestContext.Current.CancellationToken);
|
||||
var rv = RowVersionOf(dbName, db => db.DriverInstances.Single(d => d.DriverInstanceId == RawTreeTestDb.FolderedDriverId).RowVersion);
|
||||
|
||||
var result = await service.UpdateDriverAsync(RawTreeTestDb.FolderedDriverId, "modbus-2", "{}", null, rv);
|
||||
var result = await service.UpdateDriverAsync(RawTreeTestDb.FolderedDriverId, "modbus-2", "{}", null, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error!.ShouldContain("already exists");
|
||||
@@ -362,7 +362,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.UpdateDriverAsync(RawTreeTestDb.FolderedDriverId, "x", "{}", null, rv);
|
||||
var result = await service.UpdateDriverAsync(RawTreeTestDb.FolderedDriverId, "x", "{}", null, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -375,7 +375,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dto = await service.LoadDeviceForEditAsync(RawTreeTestDb.FolderedDeviceId);
|
||||
var dto = await service.LoadDeviceForEditAsync(RawTreeTestDb.FolderedDeviceId, TestContext.Current.CancellationToken);
|
||||
|
||||
dto.ShouldNotBeNull();
|
||||
dto!.Name.ShouldBe("Device1");
|
||||
@@ -388,7 +388,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
public async Task LoadDeviceForEdit_returns_null_when_missing()
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
(await service.LoadDeviceForEditAsync("DEV-nope")).ShouldBeNull();
|
||||
(await service.LoadDeviceForEditAsync("DEV-nope", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -398,7 +398,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
var rv = RowVersionOf(dbName, db => db.Devices.Single(d => d.DeviceId == RawTreeTestDb.FolderedDeviceId).RowVersion);
|
||||
|
||||
var result = await service.UpdateDeviceAsync(
|
||||
RawTreeTestDb.FolderedDeviceId, "PLC-A", "{\"Host\":\"10.0.0.9\",\"Port\":502}", enabled: false, rv);
|
||||
RawTreeTestDb.FolderedDeviceId, "PLC-A", "{\"Host\":\"10.0.0.9\",\"Port\":502}", enabled: false, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
@@ -412,10 +412,10 @@ public sealed class RawTreeServiceWaveBTests
|
||||
public async Task UpdateDevice_rejects_sibling_name_collision()
|
||||
{
|
||||
var (service, dbName) = Seeded();
|
||||
await service.CreateDeviceAsync(RawTreeTestDb.FolderedDriverId, "Device2", "{}");
|
||||
await service.CreateDeviceAsync(RawTreeTestDb.FolderedDriverId, "Device2", "{}", TestContext.Current.CancellationToken);
|
||||
var rv = RowVersionOf(dbName, db => db.Devices.Single(d => d.DeviceId == RawTreeTestDb.FolderedDeviceId).RowVersion);
|
||||
|
||||
var result = await service.UpdateDeviceAsync(RawTreeTestDb.FolderedDeviceId, "Device2", "{}", true, rv);
|
||||
var result = await service.UpdateDeviceAsync(RawTreeTestDb.FolderedDeviceId, "Device2", "{}", true, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error!.ShouldContain("already exists");
|
||||
@@ -425,17 +425,17 @@ public sealed class RawTreeServiceWaveBTests
|
||||
public async Task CreateDevice_refuses_a_second_device_under_an_MTConnect_driver_but_not_under_others()
|
||||
{
|
||||
var (service, dbName) = Seeded();
|
||||
var mt = await service.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "mt-1", "MTConnect", "{}");
|
||||
var mt = await service.CreateDriverAsync(RawTreeTestDb.ClusterId, null, "mt-1", "MTConnect", "{}", TestContext.Current.CancellationToken);
|
||||
mt.Ok.ShouldBeTrue();
|
||||
|
||||
// CreateDriver auto-creates Device1, so the very next device is the ambiguous second one (#628).
|
||||
var refused = await service.CreateDeviceAsync(mt.CreatedId!, "Device2", "{}");
|
||||
var refused = await service.CreateDeviceAsync(mt.CreatedId!, "Device2", "{}", TestContext.Current.CancellationToken);
|
||||
|
||||
refused.Ok.ShouldBeFalse();
|
||||
refused.Error!.ShouldContain("one MTConnect driver per agent device");
|
||||
|
||||
// Positive control: the same call against a non-MTConnect driver still succeeds.
|
||||
(await service.CreateDeviceAsync(RawTreeTestDb.FolderedDriverId, "Device9", "{}")).Ok.ShouldBeTrue();
|
||||
(await service.CreateDeviceAsync(RawTreeTestDb.FolderedDriverId, "Device9", "{}", TestContext.Current.CancellationToken)).Ok.ShouldBeTrue();
|
||||
using var db = RawTreeTestDb.CreateNamed(dbName);
|
||||
db.Devices.Count(d => d.DriverInstanceId == mt.CreatedId).ShouldBe(1);
|
||||
}
|
||||
@@ -454,7 +454,7 @@ public sealed class RawTreeServiceWaveBTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var probe = await service.LoadMergedProbeConfigAsync(RawTreeTestDb.FolderedDeviceId);
|
||||
var probe = await service.LoadMergedProbeConfigAsync(RawTreeTestDb.FolderedDeviceId, TestContext.Current.CancellationToken);
|
||||
|
||||
probe.ShouldNotBeNull();
|
||||
probe!.Value.DriverType.ShouldBe("Modbus");
|
||||
@@ -467,6 +467,6 @@ public sealed class RawTreeServiceWaveBTests
|
||||
public async Task LoadMergedProbeConfig_returns_null_for_unknown_device()
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
(await service.LoadMergedProbeConfigAsync("DEV-nope")).ShouldBeNull();
|
||||
(await service.LoadMergedProbeConfigAsync("DEV-nope", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public sealed class ScriptSourceServiceTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedScriptAndUsages(dbName);
|
||||
|
||||
var loaded = await service.GetScriptSourceAsync("SCRIPT-1");
|
||||
var loaded = await service.GetScriptSourceAsync("SCRIPT-1", TestContext.Current.CancellationToken);
|
||||
|
||||
loaded.ShouldNotBeNull();
|
||||
loaded.Value.SourceCode.ShouldBe("return 1;");
|
||||
@@ -109,7 +109,7 @@ public sealed class ScriptSourceServiceTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedScriptAndUsages(dbName);
|
||||
|
||||
var loaded = await service.GetScriptSourceAsync("SCRIPT-NOPE");
|
||||
var loaded = await service.GetScriptSourceAsync("SCRIPT-NOPE", TestContext.Current.CancellationToken);
|
||||
|
||||
loaded.ShouldBeNull();
|
||||
}
|
||||
@@ -123,9 +123,9 @@ public sealed class ScriptSourceServiceTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedScriptAndUsages(dbName);
|
||||
|
||||
(await service.CountVirtualTagsUsingScriptAsync("SCRIPT-1")).ShouldBe(2);
|
||||
(await service.CountVirtualTagsUsingScriptAsync("SCRIPT-2")).ShouldBe(1);
|
||||
(await service.CountVirtualTagsUsingScriptAsync("SCRIPT-NOPE")).ShouldBe(0);
|
||||
(await service.CountVirtualTagsUsingScriptAsync("SCRIPT-1", TestContext.Current.CancellationToken)).ShouldBe(2);
|
||||
(await service.CountVirtualTagsUsingScriptAsync("SCRIPT-2", TestContext.Current.CancellationToken)).ShouldBe(1);
|
||||
(await service.CountVirtualTagsUsingScriptAsync("SCRIPT-NOPE", TestContext.Current.CancellationToken)).ShouldBe(0);
|
||||
}
|
||||
|
||||
// ----- UpdateScriptSourceAsync -----
|
||||
@@ -147,7 +147,7 @@ public sealed class ScriptSourceServiceTests
|
||||
}
|
||||
|
||||
const string newSource = "return 42;";
|
||||
var result = await service.UpdateScriptSourceAsync("SCRIPT-1", newSource, rv);
|
||||
var result = await service.UpdateScriptSourceAsync("SCRIPT-1", newSource, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -166,7 +166,7 @@ public sealed class ScriptSourceServiceTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedScriptAndUsages(dbName);
|
||||
|
||||
var result = await service.UpdateScriptSourceAsync("SCRIPT-NOPE", "x", []);
|
||||
var result = await service.UpdateScriptSourceAsync("SCRIPT-NOPE", "x", [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Script not found.");
|
||||
@@ -200,7 +200,7 @@ public sealed class ScriptSourceServiceTests
|
||||
other.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.UpdateScriptSourceAsync("SCRIPT-1", "return 99;", staleRv);
|
||||
var result = await service.UpdateScriptSourceAsync("SCRIPT-1", "return 99;", staleRv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
|
||||
@@ -76,7 +76,7 @@ public sealed class UnsSegmentGuardTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.CreateAreaAsync("MAIN", "AREA-NEW", name, null);
|
||||
var result = await service.CreateAreaAsync("MAIN", "AREA-NEW", name, null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -88,7 +88,7 @@ public sealed class UnsSegmentGuardTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
(await service.CreateAreaAsync("MAIN", "AREA-NEW", name, null)).Ok.ShouldBeTrue();
|
||||
(await service.CreateAreaAsync("MAIN", "AREA-NEW", name, null, TestContext.Current.CancellationToken)).Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -99,13 +99,13 @@ public sealed class UnsSegmentGuardTests
|
||||
public async Task UpdateArea_rejects_a_rename_to_an_invalid_segment_and_leaves_the_row_alone()
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
await service.CreateAreaAsync("MAIN", "AREA-1", "packaging", null);
|
||||
await service.CreateAreaAsync("MAIN", "AREA-1", "packaging", null, TestContext.Current.CancellationToken);
|
||||
var rowVersion = Load(dbName).UnsAreas.Single(a => a.UnsAreaId == "AREA-1").RowVersion;
|
||||
|
||||
(await service.UpdateAreaAsync("AREA-1", "Packaging", null, "MAIN", rowVersion)).Ok.ShouldBeFalse();
|
||||
(await service.UpdateAreaAsync("AREA-1", "Packaging", null, "MAIN", rowVersion, TestContext.Current.CancellationToken)).Ok.ShouldBeFalse();
|
||||
Load(dbName).UnsAreas.Single(a => a.UnsAreaId == "AREA-1").Name.ShouldBe("packaging");
|
||||
|
||||
(await service.UpdateAreaAsync("AREA-1", "assembly", null, "MAIN", rowVersion)).Ok.ShouldBeTrue();
|
||||
(await service.UpdateAreaAsync("AREA-1", "assembly", null, "MAIN", rowVersion, TestContext.Current.CancellationToken)).Ok.ShouldBeTrue();
|
||||
Load(dbName).UnsAreas.Single(a => a.UnsAreaId == "AREA-1").Name.ShouldBe("assembly");
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ public sealed class UnsSegmentGuardTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedAreaAndLine(dbName);
|
||||
|
||||
var result = await service.CreateLineAsync("AREA-1", "LINE-NEW", name, null);
|
||||
var result = await service.CreateLineAsync("AREA-1", "LINE-NEW", name, null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -130,7 +130,7 @@ public sealed class UnsSegmentGuardTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedAreaAndLine(dbName);
|
||||
|
||||
(await service.CreateLineAsync("AREA-1", "LINE-NEW", "line-2", null)).Ok.ShouldBeTrue();
|
||||
(await service.CreateLineAsync("AREA-1", "LINE-NEW", "line-2", null, TestContext.Current.CancellationToken)).Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -140,8 +140,8 @@ public sealed class UnsSegmentGuardTests
|
||||
SeedAreaAndLine(dbName);
|
||||
var rowVersion = Load(dbName).UnsLines.Single(l => l.UnsLineId == "LINE-1").RowVersion;
|
||||
|
||||
(await service.UpdateLineAsync("LINE-1", "Line 1", null, "AREA-1", rowVersion)).Ok.ShouldBeFalse();
|
||||
(await service.UpdateLineAsync("LINE-1", "line-1", null, "AREA-1", rowVersion)).Ok.ShouldBeTrue();
|
||||
(await service.UpdateLineAsync("LINE-1", "Line 1", null, "AREA-1", rowVersion, TestContext.Current.CancellationToken)).Ok.ShouldBeFalse();
|
||||
(await service.UpdateLineAsync("LINE-1", "line-1", null, "AREA-1", rowVersion, TestContext.Current.CancellationToken)).Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
// --- Equipment ----------------------------------------------------------------------------
|
||||
@@ -153,7 +153,7 @@ public sealed class UnsSegmentGuardTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedAreaAndLine(dbName);
|
||||
|
||||
(await service.CreateEquipmentAsync(Input(name, "mc_1"))).Ok.ShouldBeFalse();
|
||||
(await service.CreateEquipmentAsync(Input(name, "mc_1"), TestContext.Current.CancellationToken)).Ok.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -162,7 +162,7 @@ public sealed class UnsSegmentGuardTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedAreaAndLine(dbName);
|
||||
|
||||
(await service.CreateEquipmentAsync(Input("packer-1", "mc_1"))).Ok.ShouldBeTrue();
|
||||
(await service.CreateEquipmentAsync(Input("packer-1", "mc_1"), TestContext.Current.CancellationToken)).Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -170,14 +170,14 @@ public sealed class UnsSegmentGuardTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedAreaAndLine(dbName);
|
||||
var created = await service.CreateEquipmentAsync(Input("packer-1", "mc_1"));
|
||||
var created = await service.CreateEquipmentAsync(Input("packer-1", "mc_1"), TestContext.Current.CancellationToken);
|
||||
created.Ok.ShouldBeTrue();
|
||||
|
||||
var row = Load(dbName).Equipment.Single();
|
||||
|
||||
(await service.UpdateEquipmentAsync(row.EquipmentId, Input("Packer 1", "mc_1"), row.RowVersion))
|
||||
(await service.UpdateEquipmentAsync(row.EquipmentId, Input("Packer 1", "mc_1"), row.RowVersion, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeFalse();
|
||||
(await service.UpdateEquipmentAsync(row.EquipmentId, Input("packer-2", "mc_1"), row.RowVersion))
|
||||
(await service.UpdateEquipmentAsync(row.EquipmentId, Input("packer-2", "mc_1"), row.RowVersion, TestContext.Current.CancellationToken))
|
||||
.Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ public sealed class UnsSegmentGuardTests
|
||||
Input("packer-1", "mc_1"),
|
||||
Input("Packer 2", "mc_2"),
|
||||
Input("packer-3", "mc_3"),
|
||||
]);
|
||||
], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Inserted.ShouldBe(2);
|
||||
result.Skipped.ShouldBe(0, "a bad name is not a row the operator already has");
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
|
||||
var result = await service.CreateAreaAsync("MAIN", "AREA-NEW", "assembly", " ");
|
||||
var result = await service.CreateAreaAsync("MAIN", "AREA-NEW", "assembly", " ", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -49,9 +49,9 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
public async Task CreateArea_duplicate_id_returns_error()
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
await service.CreateAreaAsync("MAIN", "AREA-DUP", "first", null);
|
||||
await service.CreateAreaAsync("MAIN", "AREA-DUP", "first", null, TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await service.CreateAreaAsync("MAIN", "AREA-DUP", "second", null);
|
||||
var result = await service.CreateAreaAsync("MAIN", "AREA-DUP", "second", null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Area 'AREA-DUP' already exists.");
|
||||
@@ -67,7 +67,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
public async Task UpdateArea_changes_name_and_notes()
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
await service.CreateAreaAsync("MAIN", "AREA-1", "old", "old notes");
|
||||
await service.CreateAreaAsync("MAIN", "AREA-1", "old", "old notes", TestContext.Current.CancellationToken);
|
||||
|
||||
byte[] rv;
|
||||
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
||||
@@ -75,7 +75,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
rv = db.UnsAreas.Single(a => a.UnsAreaId == "AREA-1").RowVersion;
|
||||
}
|
||||
|
||||
var result = await service.UpdateAreaAsync("AREA-1", "new", " ", "MAIN", rv);
|
||||
var result = await service.UpdateAreaAsync("AREA-1", "new", " ", "MAIN", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -116,7 +116,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
rv = db.UnsAreas.Single(a => a.UnsAreaId == "AREA-X").RowVersion;
|
||||
}
|
||||
|
||||
var result = await service.UpdateAreaAsync("AREA-X", "a", null, "SITE-A", rv);
|
||||
var result = await service.UpdateAreaAsync("AREA-X", "a", null, "SITE-A", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -132,7 +132,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.UpdateAreaAsync("NOPE", "n", null, "MAIN", []);
|
||||
var result = await service.UpdateAreaAsync("NOPE", "n", null, "MAIN", [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Row no longer exists.");
|
||||
@@ -145,7 +145,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
public async Task DeleteArea_removes_row()
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
await service.CreateAreaAsync("MAIN", "AREA-DEL", "a", null);
|
||||
await service.CreateAreaAsync("MAIN", "AREA-DEL", "a", null, TestContext.Current.CancellationToken);
|
||||
|
||||
byte[] rv;
|
||||
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
||||
@@ -153,7 +153,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
rv = db.UnsAreas.Single(a => a.UnsAreaId == "AREA-DEL").RowVersion;
|
||||
}
|
||||
|
||||
var result = await service.DeleteAreaAsync("AREA-DEL", rv);
|
||||
var result = await service.DeleteAreaAsync("AREA-DEL", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -168,7 +168,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.DeleteAreaAsync("GHOST", []);
|
||||
var result = await service.DeleteAreaAsync("GHOST", [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -181,9 +181,9 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
public async Task CreateLine_then_load_shows_it()
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
await service.CreateAreaAsync("MAIN", "AREA-1", "a", null);
|
||||
await service.CreateAreaAsync("MAIN", "AREA-1", "a", null, TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await service.CreateLineAsync("AREA-1", "LINE-NEW", "line-a", " ");
|
||||
var result = await service.CreateLineAsync("AREA-1", "LINE-NEW", "line-a", " ", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -200,9 +200,9 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
public async Task CreateLine_duplicate_id_returns_error()
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
await service.CreateLineAsync("AREA-1", "LINE-DUP", "first", null);
|
||||
await service.CreateLineAsync("AREA-1", "LINE-DUP", "first", null, TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await service.CreateLineAsync("AREA-1", "LINE-DUP", "second", null);
|
||||
var result = await service.CreateLineAsync("AREA-1", "LINE-DUP", "second", null, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Line 'LINE-DUP' already exists.");
|
||||
@@ -215,7 +215,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
public async Task UpdateLine_changes_area_name_and_notes()
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
await service.CreateLineAsync("AREA-1", "LINE-1", "old", "old notes");
|
||||
await service.CreateLineAsync("AREA-1", "LINE-1", "old", "old notes", TestContext.Current.CancellationToken);
|
||||
|
||||
byte[] rv;
|
||||
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
||||
@@ -223,7 +223,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
rv = db.UnsLines.Single(l => l.UnsLineId == "LINE-1").RowVersion;
|
||||
}
|
||||
|
||||
var result = await service.UpdateLineAsync("LINE-1", "new", " ", "AREA-2", rv);
|
||||
var result = await service.UpdateLineAsync("LINE-1", "new", " ", "AREA-2", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -241,7 +241,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.UpdateLineAsync("NOPE", "n", null, "AREA-1", []);
|
||||
var result = await service.UpdateLineAsync("NOPE", "n", null, "AREA-1", [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Row no longer exists.");
|
||||
@@ -275,7 +275,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
rv = db.UnsLines.Single(l => l.UnsLineId == "LINE-BOUND").RowVersion;
|
||||
}
|
||||
|
||||
var result = await service.UpdateLineAsync("LINE-BOUND", "line", null, "A2-SITE-A", rv);
|
||||
var result = await service.UpdateLineAsync("LINE-BOUND", "line", null, "A2-SITE-A", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -292,7 +292,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
public async Task DeleteLine_removes_row()
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
await service.CreateLineAsync("AREA-1", "LINE-DEL", "l", null);
|
||||
await service.CreateLineAsync("AREA-1", "LINE-DEL", "l", null, TestContext.Current.CancellationToken);
|
||||
|
||||
byte[] rv;
|
||||
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
||||
@@ -300,7 +300,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
rv = db.UnsLines.Single(l => l.UnsLineId == "LINE-DEL").RowVersion;
|
||||
}
|
||||
|
||||
var result = await service.DeleteLineAsync("LINE-DEL", rv);
|
||||
var result = await service.DeleteLineAsync("LINE-DEL", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -315,7 +315,7 @@ public sealed class UnsTreeServiceAreaLineTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.DeleteLineAsync("GHOST", []);
|
||||
var result = await service.DeleteLineAsync("GHOST", [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed class UnsTreeServiceCreateScriptTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
|
||||
var result = await service.CreateScriptAsync("my new script");
|
||||
var result = await service.CreateScriptAsync("my new script", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -59,7 +59,7 @@ public sealed class UnsTreeServiceCreateScriptTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
|
||||
var result = await service.CreateScriptAsync(" ");
|
||||
var result = await service.CreateScriptAsync(" ", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
using var db = UnsTreeTestDb.CreateNamed(dbName);
|
||||
@@ -72,8 +72,8 @@ public sealed class UnsTreeServiceCreateScriptTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
|
||||
var first = await service.CreateScriptAsync("first");
|
||||
var second = await service.CreateScriptAsync("second");
|
||||
var first = await service.CreateScriptAsync("first", TestContext.Current.CancellationToken);
|
||||
var second = await service.CreateScriptAsync("second", TestContext.Current.CancellationToken);
|
||||
|
||||
first.Ok.ShouldBeTrue();
|
||||
second.Ok.ShouldBeTrue();
|
||||
|
||||
+8
-8
@@ -50,7 +50,7 @@ public sealed class UnsTreeServiceDeleteClusterTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.DeleteClusterAsync("CL-EMPTY");
|
||||
var result = await service.DeleteClusterAsync("CL-EMPTY", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -65,7 +65,7 @@ public sealed class UnsTreeServiceDeleteClusterTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.DeleteClusterAsync("GHOST-CLUSTER");
|
||||
var result = await service.DeleteClusterAsync("GHOST-CLUSTER", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -83,7 +83,7 @@ public sealed class UnsTreeServiceDeleteClusterTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.DeleteClusterAsync("CL-AREA");
|
||||
var result = await service.DeleteClusterAsync("CL-AREA", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -112,7 +112,7 @@ public sealed class UnsTreeServiceDeleteClusterTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.DeleteClusterAsync("CL-DRV");
|
||||
var result = await service.DeleteClusterAsync("CL-DRV", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -140,7 +140,7 @@ public sealed class UnsTreeServiceDeleteClusterTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.DeleteClusterAsync("CL-NODE");
|
||||
var result = await service.DeleteClusterAsync("CL-NODE", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -171,7 +171,7 @@ public sealed class UnsTreeServiceDeleteClusterTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.DeleteEnterpriseAsync("ent-1");
|
||||
var result = await service.DeleteEnterpriseAsync("ent-1", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -187,7 +187,7 @@ public sealed class UnsTreeServiceDeleteClusterTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.DeleteEnterpriseAsync("nonexistent-enterprise");
|
||||
var result = await service.DeleteEnterpriseAsync("nonexistent-enterprise", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -209,7 +209,7 @@ public sealed class UnsTreeServiceDeleteClusterTests
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
var result = await service.DeleteEnterpriseAsync("ent-3");
|
||||
var result = await service.DeleteEnterpriseAsync("ent-3", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ public sealed class UnsTreeServiceEquipmentChildRowsTests
|
||||
[Fact]
|
||||
public async Task LoadVirtualTagsForEquipment_returns_vtags_in_name_order()
|
||||
{
|
||||
var rows = await SeededService().LoadVirtualTagsForEquipmentAsync(UnsTreeTestDb.SeededEquipmentId);
|
||||
var rows = await SeededService().LoadVirtualTagsForEquipmentAsync(UnsTreeTestDb.SeededEquipmentId, TestContext.Current.CancellationToken);
|
||||
rows.Count.ShouldBe(1);
|
||||
rows[0].VirtualTagId.ShouldBe("VTAG-1");
|
||||
rows[0].Name.ShouldBe("computed");
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ public sealed class UnsTreeServiceEquipmentPickContextTests
|
||||
[Fact]
|
||||
public async Task ResolvesClusterLines_forKnownLine()
|
||||
{
|
||||
var ctx = await SeededService().LoadEquipmentPickContextAsync("LINE-1");
|
||||
var ctx = await SeededService().LoadEquipmentPickContextAsync("LINE-1", TestContext.Current.CancellationToken);
|
||||
ctx.Lines.ShouldContain(o => o.Id == "LINE-1");
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class UnsTreeServiceEquipmentPickContextTests
|
||||
public async Task Empty_for_null_or_unknown_line()
|
||||
{
|
||||
var svc = SeededService();
|
||||
(await svc.LoadEquipmentPickContextAsync(null)).Lines.ShouldBeEmpty();
|
||||
(await svc.LoadEquipmentPickContextAsync("NOPE")).Lines.ShouldBeEmpty();
|
||||
(await svc.LoadEquipmentPickContextAsync(null, TestContext.Current.CancellationToken)).Lines.ShouldBeEmpty();
|
||||
(await svc.LoadEquipmentPickContextAsync("NOPE", TestContext.Current.CancellationToken)).Lines.ShouldBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedLine(dbName, "MAIN");
|
||||
|
||||
var result = await service.CreateEquipmentAsync(Input("machine-1", "machine_001", "LINE-1"));
|
||||
var result = await service.CreateEquipmentAsync(Input("machine-1", "machine_001", "LINE-1"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -68,9 +68,9 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedLine(dbName, "MAIN");
|
||||
await service.CreateEquipmentAsync(Input("machine-1", "machine_dup", "LINE-1"));
|
||||
await service.CreateEquipmentAsync(Input("machine-1", "machine_dup", "LINE-1"), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await service.CreateEquipmentAsync(Input("machine-2", "machine_dup", "LINE-1"));
|
||||
var result = await service.CreateEquipmentAsync(Input("machine-2", "machine_dup", "LINE-1"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("MachineCode 'machine_dup' already exists in this fleet.");
|
||||
@@ -82,7 +82,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.CreateEquipmentAsync(Input("machine-1", "machine_001", ""));
|
||||
var result = await service.CreateEquipmentAsync(Input("machine-1", "machine_001", ""), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Pick a UNS line.");
|
||||
@@ -99,7 +99,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
var input = new EquipmentInput("machine-2", "machine_002", "LINE-1",
|
||||
null, null, null, null, null, null, null, null, null, null, null, true);
|
||||
|
||||
var result = await service.CreateEquipmentAsync(input);
|
||||
var result = await service.CreateEquipmentAsync(input, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.CreatedId.ShouldNotBeNull();
|
||||
@@ -114,7 +114,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedLine(dbName, "MAIN");
|
||||
await service.CreateEquipmentAsync(Input("machine-1", "machine_001", "LINE-1"));
|
||||
await service.CreateEquipmentAsync(Input("machine-1", "machine_001", "LINE-1"), TestContext.Current.CancellationToken);
|
||||
|
||||
string equipmentId;
|
||||
byte[] rv;
|
||||
@@ -130,7 +130,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
HardwareRevision: null, SoftwareRevision: null, YearOfConstruction: (short)2021,
|
||||
AssetLocation: null, ManufacturerUri: null, DeviceManualUri: null, Enabled: false);
|
||||
|
||||
var result = await service.UpdateEquipmentAsync(equipmentId, updated, rv);
|
||||
var result = await service.UpdateEquipmentAsync(equipmentId, updated, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -150,7 +150,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.UpdateEquipmentAsync("EQ-nope", Input("n", "mc", "LINE-1"), []);
|
||||
var result = await service.UpdateEquipmentAsync("EQ-nope", Input("n", "mc", "LINE-1"), [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Row no longer exists.");
|
||||
@@ -162,8 +162,8 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedLine(dbName, "MAIN");
|
||||
await service.CreateEquipmentAsync(Input("machine-a", "mc_a", "LINE-1"));
|
||||
await service.CreateEquipmentAsync(Input("machine-b", "mc_b", "LINE-1"));
|
||||
await service.CreateEquipmentAsync(Input("machine-a", "mc_a", "LINE-1"), TestContext.Current.CancellationToken);
|
||||
await service.CreateEquipmentAsync(Input("machine-b", "mc_b", "LINE-1"), TestContext.Current.CancellationToken);
|
||||
|
||||
string equipmentId;
|
||||
byte[] rv;
|
||||
@@ -176,7 +176,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
|
||||
// Try to rename mc_a → mc_b (which already exists).
|
||||
var result = await service.UpdateEquipmentAsync(
|
||||
equipmentId, Input("machine-a", "mc_b", "LINE-1"), rv);
|
||||
equipmentId, Input("machine-a", "mc_b", "LINE-1"), rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -195,7 +195,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedLine(dbName, "MAIN");
|
||||
await service.CreateEquipmentAsync(Input("machine-1", "machine_001", "LINE-1"));
|
||||
await service.CreateEquipmentAsync(Input("machine-1", "machine_001", "LINE-1"), TestContext.Current.CancellationToken);
|
||||
|
||||
string equipmentId;
|
||||
byte[] rv;
|
||||
@@ -206,7 +206,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
rv = eq.RowVersion;
|
||||
}
|
||||
|
||||
var result = await service.DeleteEquipmentAsync(equipmentId, rv);
|
||||
var result = await service.DeleteEquipmentAsync(equipmentId, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -221,7 +221,7 @@ public sealed class UnsTreeServiceEquipmentTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.DeleteEquipmentAsync("EQ-ghost", []);
|
||||
var result = await service.DeleteEquipmentAsync("EQ-ghost", [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
|
||||
@@ -46,7 +46,7 @@ public sealed class UnsTreeServiceImportTests
|
||||
[
|
||||
Input("machine-1", "mc_1", "LINE-1"),
|
||||
Input("machine-2", "mc_2", "LINE-1"),
|
||||
]);
|
||||
], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Inserted.ShouldBe(2);
|
||||
result.Skipped.ShouldBe(0);
|
||||
@@ -72,14 +72,14 @@ public sealed class UnsTreeServiceImportTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedLine(dbName, "MAIN");
|
||||
// Pre-existing equipment with MachineCode "mc_existing".
|
||||
await service.CreateEquipmentAsync(Input("machine-x", "mc_existing", "LINE-1"));
|
||||
await service.CreateEquipmentAsync(Input("machine-x", "mc_existing", "LINE-1"), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await service.ImportEquipmentAsync(
|
||||
[
|
||||
Input("machine-1", "mc_existing", "LINE-1"), // dup of DB row → skip
|
||||
Input("machine-2", "mc_new", "LINE-1"), // inserts
|
||||
Input("machine-3", "mc_new", "LINE-1"), // dup of in-batch row → skip
|
||||
]);
|
||||
], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Inserted.ShouldBe(1);
|
||||
result.Skipped.ShouldBe(2);
|
||||
@@ -101,7 +101,7 @@ public sealed class UnsTreeServiceImportTests
|
||||
[
|
||||
Input("machine-1", "mc_1", "LINE-BOGUS"),
|
||||
Input("machine-2", "mc_2", "LINE-1"),
|
||||
]);
|
||||
], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Inserted.ShouldBe(1);
|
||||
result.Skipped.ShouldBe(0);
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dto = await service.LoadAreaAsync("AREA-1");
|
||||
var dto = await service.LoadAreaAsync("AREA-1", TestContext.Current.CancellationToken);
|
||||
|
||||
dto.ShouldNotBeNull();
|
||||
dto.UnsAreaId.ShouldBe("AREA-1");
|
||||
@@ -41,7 +41,7 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
(await service.LoadAreaAsync("NOPE")).ShouldBeNull();
|
||||
(await service.LoadAreaAsync("NOPE", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Loading a seeded line maps its fields, parent area, and a non-empty RowVersion.</summary>
|
||||
@@ -50,7 +50,7 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dto = await service.LoadLineAsync("LINE-1");
|
||||
var dto = await service.LoadLineAsync("LINE-1", TestContext.Current.CancellationToken);
|
||||
|
||||
dto.ShouldNotBeNull();
|
||||
dto.UnsLineId.ShouldBe("LINE-1");
|
||||
@@ -65,7 +65,7 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
(await service.LoadLineAsync("NOPE")).ShouldBeNull();
|
||||
(await service.LoadLineAsync("NOPE", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Loading the seeded equipment maps its identity fields, line, and a non-empty RowVersion.</summary>
|
||||
@@ -74,7 +74,7 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dto = await service.LoadEquipmentAsync(UnsTreeTestDb.SeededEquipmentId);
|
||||
var dto = await service.LoadEquipmentAsync(UnsTreeTestDb.SeededEquipmentId, TestContext.Current.CancellationToken);
|
||||
|
||||
dto.ShouldNotBeNull();
|
||||
dto.EquipmentId.ShouldBe(UnsTreeTestDb.SeededEquipmentId);
|
||||
@@ -90,7 +90,7 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
(await service.LoadEquipmentAsync("NOPE")).ShouldBeNull();
|
||||
(await service.LoadEquipmentAsync("NOPE", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,10 +129,10 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
DriverType = "S7",
|
||||
DriverConfig = "{}",
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var drivers = await service.LoadDriversForClusterAsync(UnsTreeTestDb.PopulatedClusterId);
|
||||
var drivers = await service.LoadDriversForClusterAsync(UnsTreeTestDb.PopulatedClusterId, TestContext.Current.CancellationToken);
|
||||
|
||||
drivers.Count.ShouldBe(2);
|
||||
drivers[0].DriverInstanceId.ShouldBe("DRV-A");
|
||||
@@ -151,7 +151,7 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dto = await service.LoadTagAsync("TAG-1");
|
||||
var dto = await service.LoadTagAsync("TAG-1", TestContext.Current.CancellationToken);
|
||||
|
||||
dto.ShouldNotBeNull();
|
||||
dto.TagId.ShouldBe("TAG-1");
|
||||
@@ -170,7 +170,7 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
(await service.LoadTagAsync("NOPE")).ShouldBeNull();
|
||||
(await service.LoadTagAsync("NOPE", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>Loading a seeded virtual tag maps its fields, owning equipment, and a non-empty RowVersion.</summary>
|
||||
@@ -179,7 +179,7 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
var dto = await service.LoadVirtualTagAsync("VTAG-1");
|
||||
var dto = await service.LoadVirtualTagAsync("VTAG-1", TestContext.Current.CancellationToken);
|
||||
|
||||
dto.ShouldNotBeNull();
|
||||
dto.VirtualTagId.ShouldBe("VTAG-1");
|
||||
@@ -196,6 +196,6 @@ public sealed class UnsTreeServiceLoadEditTests
|
||||
{
|
||||
var (service, _) = Seeded();
|
||||
|
||||
(await service.LoadVirtualTagAsync("NOPE")).ShouldBeNull();
|
||||
(await service.LoadVirtualTagAsync("NOPE", TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
Seed(name);
|
||||
var service = new UnsTreeService(Factory(name));
|
||||
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed", "TAG-running"]);
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed", "TAG-running"], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -127,9 +127,9 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
var name = $"ref-load-{Guid.NewGuid():N}";
|
||||
Seed(name);
|
||||
var service = new UnsTreeService(Factory(name));
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed", "TAG-grouped"]);
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed", "TAG-grouped"], TestContext.Current.CancellationToken);
|
||||
|
||||
var rows = await service.LoadReferencesForEquipmentAsync(EquipmentId);
|
||||
var rows = await service.LoadReferencesForEquipmentAsync(EquipmentId, TestContext.Current.CancellationToken);
|
||||
|
||||
rows.Count.ShouldBe(2);
|
||||
|
||||
@@ -151,7 +151,7 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
Seed(name);
|
||||
var service = new UnsTreeService(Factory(name));
|
||||
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-other"]);
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-other"], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -168,9 +168,9 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
var name = $"ref-dup-{Guid.NewGuid():N}";
|
||||
Seed(name);
|
||||
var service = new UnsTreeService(Factory(name));
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed"]);
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed"], TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed"]);
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed"], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -189,7 +189,7 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
var guard = new FakeGuard { Result = "'speed' already exists on equipment 'machine-1'." };
|
||||
var service = new UnsTreeService(Factory(name), guard);
|
||||
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed"]);
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed"], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("'speed' already exists on equipment 'machine-1'.");
|
||||
@@ -211,7 +211,7 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
var guard = new FakeGuard();
|
||||
var service = new UnsTreeService(Factory(name), guard);
|
||||
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed", "TAG-running", "TAG-grouped"]);
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed", "TAG-running", "TAG-grouped"], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
guard.CheckManyCalls.ShouldBe(1);
|
||||
@@ -230,7 +230,7 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
var guard = new FakeGuard { Result = "'running' already exists on equipment 'machine-1'." };
|
||||
var service = new UnsTreeService(Factory(name), guard);
|
||||
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed", "TAG-running"]);
|
||||
var result = await service.AddReferencesAsync(EquipmentId, ["TAG-speed", "TAG-running"], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("'running' already exists on equipment 'machine-1'.");
|
||||
@@ -249,22 +249,22 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
var name = $"ref-override-{Guid.NewGuid():N}";
|
||||
Seed(name);
|
||||
var service = new UnsTreeService(Factory(name));
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed"]);
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed"], TestContext.Current.CancellationToken);
|
||||
|
||||
var row = (await service.LoadReferencesForEquipmentAsync(EquipmentId)).Single();
|
||||
var row = (await service.LoadReferencesForEquipmentAsync(EquipmentId, TestContext.Current.CancellationToken)).Single();
|
||||
|
||||
var set = await service.SetReferenceOverrideAsync(row.UnsTagReferenceId, "line-speed", row.RowVersion);
|
||||
var set = await service.SetReferenceOverrideAsync(row.UnsTagReferenceId, "line-speed", row.RowVersion, TestContext.Current.CancellationToken);
|
||||
set.Ok.ShouldBeTrue();
|
||||
|
||||
var afterSet = (await service.LoadReferencesForEquipmentAsync(EquipmentId)).Single();
|
||||
var afterSet = (await service.LoadReferencesForEquipmentAsync(EquipmentId, TestContext.Current.CancellationToken)).Single();
|
||||
afterSet.EffectiveName.ShouldBe("line-speed");
|
||||
afterSet.DisplayNameOverride.ShouldBe("line-speed");
|
||||
afterSet.RawPath.ShouldBe("MAIN/dev1/speed"); // raw path is unchanged by an override
|
||||
|
||||
var clear = await service.SetReferenceOverrideAsync(afterSet.UnsTagReferenceId, " ", afterSet.RowVersion);
|
||||
var clear = await service.SetReferenceOverrideAsync(afterSet.UnsTagReferenceId, " ", afterSet.RowVersion, TestContext.Current.CancellationToken);
|
||||
clear.Ok.ShouldBeTrue();
|
||||
|
||||
var afterClear = (await service.LoadReferencesForEquipmentAsync(EquipmentId)).Single();
|
||||
var afterClear = (await service.LoadReferencesForEquipmentAsync(EquipmentId, TestContext.Current.CancellationToken)).Single();
|
||||
afterClear.EffectiveName.ShouldBe("speed");
|
||||
afterClear.DisplayNameOverride.ShouldBeNull();
|
||||
}
|
||||
@@ -277,11 +277,11 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
Seed(name);
|
||||
var guard = new FakeGuard();
|
||||
var service = new UnsTreeService(Factory(name), guard);
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed"]);
|
||||
var row = (await service.LoadReferencesForEquipmentAsync(EquipmentId)).Single();
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed"], TestContext.Current.CancellationToken);
|
||||
var row = (await service.LoadReferencesForEquipmentAsync(EquipmentId, TestContext.Current.CancellationToken)).Single();
|
||||
|
||||
guard.Result = "collides with a virtual tag.";
|
||||
var result = await service.SetReferenceOverrideAsync(row.UnsTagReferenceId, "running", row.RowVersion);
|
||||
var result = await service.SetReferenceOverrideAsync(row.UnsTagReferenceId, "running", row.RowVersion, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("collides with a virtual tag.");
|
||||
@@ -297,17 +297,17 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
var name = $"ref-remove-{Guid.NewGuid():N}";
|
||||
Seed(name);
|
||||
var service = new UnsTreeService(Factory(name));
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed"]);
|
||||
var row = (await service.LoadReferencesForEquipmentAsync(EquipmentId)).Single();
|
||||
await service.AddReferencesAsync(EquipmentId, ["TAG-speed"], TestContext.Current.CancellationToken);
|
||||
var row = (await service.LoadReferencesForEquipmentAsync(EquipmentId, TestContext.Current.CancellationToken)).Single();
|
||||
|
||||
var result = await service.RemoveReferenceAsync(row.UnsTagReferenceId, row.RowVersion);
|
||||
var result = await service.RemoveReferenceAsync(row.UnsTagReferenceId, row.RowVersion, TestContext.Current.CancellationToken);
|
||||
result.Ok.ShouldBeTrue();
|
||||
|
||||
using var db = Db(name);
|
||||
db.UnsTagReferences.Any(r => r.EquipmentId == EquipmentId).ShouldBeFalse();
|
||||
|
||||
// Already gone → idempotent success.
|
||||
var again = await service.RemoveReferenceAsync(row.UnsTagReferenceId, row.RowVersion);
|
||||
var again = await service.RemoveReferenceAsync(row.UnsTagReferenceId, row.RowVersion, TestContext.Current.CancellationToken);
|
||||
again.Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
Seed(name);
|
||||
var service = new UnsTreeService(Factory(name));
|
||||
|
||||
var root = await service.LoadReferencePickerRootAsync(EquipmentId);
|
||||
var root = await service.LoadReferencePickerRootAsync(EquipmentId, TestContext.Current.CancellationToken);
|
||||
|
||||
root.ShouldNotBeNull();
|
||||
root!.Kind.ShouldBe(RawNodeKind.Cluster);
|
||||
@@ -347,7 +347,7 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
EntityId = "DEV-1",
|
||||
};
|
||||
|
||||
var ids = await service.LoadDescendantTagIdsAsync(deviceNode);
|
||||
var ids = await service.LoadDescendantTagIdsAsync(deviceNode, TestContext.Current.CancellationToken);
|
||||
|
||||
ids.ShouldBe(new[] { "TAG-speed", "TAG-running", "TAG-grouped" }, ignoreOrder: true);
|
||||
}
|
||||
@@ -369,7 +369,7 @@ public sealed class UnsTreeServiceReferenceTests
|
||||
EntityId = "TG-1",
|
||||
};
|
||||
|
||||
var ids = await service.LoadDescendantTagIdsAsync(groupNode);
|
||||
var ids = await service.LoadDescendantTagIdsAsync(groupNode, TestContext.Current.CancellationToken);
|
||||
|
||||
ids.ShouldBe(new[] { "TAG-grouped" });
|
||||
}
|
||||
|
||||
+18
-18
@@ -21,16 +21,16 @@ public sealed class UnsTreeServiceScriptedAlarmTests
|
||||
public async Task Create_then_list_and_load_roundtrips()
|
||||
{
|
||||
var svc = SeededService();
|
||||
var create = await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample());
|
||||
var create = await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample(), TestContext.Current.CancellationToken);
|
||||
create.Ok.ShouldBeTrue();
|
||||
create.CreatedId.ShouldBe("SA-1");
|
||||
|
||||
var rows = await svc.LoadAlarmsForEquipmentAsync(UnsTreeTestDb.SeededEquipmentId);
|
||||
var rows = await svc.LoadAlarmsForEquipmentAsync(UnsTreeTestDb.SeededEquipmentId, TestContext.Current.CancellationToken);
|
||||
rows.Count.ShouldBe(1);
|
||||
rows[0].Name.ShouldBe("Over-temp");
|
||||
rows[0].Severity.ShouldBe(700);
|
||||
|
||||
var dto = await svc.LoadScriptedAlarmAsync("SA-1");
|
||||
var dto = await svc.LoadScriptedAlarmAsync("SA-1", TestContext.Current.CancellationToken);
|
||||
dto.ShouldNotBeNull();
|
||||
dto!.EquipmentId.ShouldBe(UnsTreeTestDb.SeededEquipmentId);
|
||||
dto.HistorizeToAveva.ShouldBeTrue();
|
||||
@@ -41,8 +41,8 @@ public sealed class UnsTreeServiceScriptedAlarmTests
|
||||
public async Task Create_rejects_duplicate_id()
|
||||
{
|
||||
var svc = SeededService();
|
||||
(await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample())).Ok.ShouldBeTrue();
|
||||
var dup = await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample());
|
||||
(await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample(), TestContext.Current.CancellationToken)).Ok.ShouldBeTrue();
|
||||
var dup = await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample(), TestContext.Current.CancellationToken);
|
||||
dup.Ok.ShouldBeFalse();
|
||||
dup.Error.ShouldNotBeNull();
|
||||
}
|
||||
@@ -51,14 +51,14 @@ public sealed class UnsTreeServiceScriptedAlarmTests
|
||||
public async Task Update_changes_fields()
|
||||
{
|
||||
var svc = SeededService();
|
||||
await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample());
|
||||
var dto = await svc.LoadScriptedAlarmAsync("SA-1");
|
||||
await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample(), TestContext.Current.CancellationToken);
|
||||
var dto = await svc.LoadScriptedAlarmAsync("SA-1", TestContext.Current.CancellationToken);
|
||||
|
||||
var upd = await svc.UpdateScriptedAlarmAsync("SA-1",
|
||||
Sample() with { Name = "Renamed", Severity = 250, HistorizeToAveva = false }, dto!.RowVersion);
|
||||
Sample() with { Name = "Renamed", Severity = 250, HistorizeToAveva = false }, dto!.RowVersion, TestContext.Current.CancellationToken);
|
||||
upd.Ok.ShouldBeTrue();
|
||||
|
||||
var after = await svc.LoadScriptedAlarmAsync("SA-1");
|
||||
var after = await svc.LoadScriptedAlarmAsync("SA-1", TestContext.Current.CancellationToken);
|
||||
after!.Name.ShouldBe("Renamed");
|
||||
after.Severity.ShouldBe(250);
|
||||
after.HistorizeToAveva.ShouldBeFalse();
|
||||
@@ -68,20 +68,20 @@ public sealed class UnsTreeServiceScriptedAlarmTests
|
||||
public async Task Delete_removes_row()
|
||||
{
|
||||
var svc = SeededService();
|
||||
await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample());
|
||||
var dto = await svc.LoadScriptedAlarmAsync("SA-1");
|
||||
await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample(), TestContext.Current.CancellationToken);
|
||||
var dto = await svc.LoadScriptedAlarmAsync("SA-1", TestContext.Current.CancellationToken);
|
||||
|
||||
(await svc.DeleteScriptedAlarmAsync("SA-1", dto!.RowVersion)).Ok.ShouldBeTrue();
|
||||
(await svc.LoadAlarmsForEquipmentAsync(UnsTreeTestDb.SeededEquipmentId)).ShouldBeEmpty();
|
||||
(await svc.DeleteScriptedAlarmAsync("SA-1", dto!.RowVersion, TestContext.Current.CancellationToken)).Ok.ShouldBeTrue();
|
||||
(await svc.LoadAlarmsForEquipmentAsync(UnsTreeTestDb.SeededEquipmentId, TestContext.Current.CancellationToken)).ShouldBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_rejects_duplicate_name_on_same_equipment()
|
||||
{
|
||||
var svc = SeededService();
|
||||
(await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample("SA-1"))).Ok.ShouldBeTrue();
|
||||
(await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample("SA-1"), TestContext.Current.CancellationToken)).Ok.ShouldBeTrue();
|
||||
// Same Name ("Over-temp" from Sample), different id, same equipment → rejected.
|
||||
var dup = await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample("SA-2"));
|
||||
var dup = await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample("SA-2"), TestContext.Current.CancellationToken);
|
||||
dup.Ok.ShouldBeFalse();
|
||||
dup.Error.ShouldNotBeNull();
|
||||
dup.Error!.ShouldContain("already exists");
|
||||
@@ -91,10 +91,10 @@ public sealed class UnsTreeServiceScriptedAlarmTests
|
||||
public async Task LoadAlarmsForEquipment_excludes_other_equipment()
|
||||
{
|
||||
var svc = SeededService();
|
||||
await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample("SA-MINE"));
|
||||
await svc.CreateScriptedAlarmAsync("EQ-OTHER", Sample("SA-OTHER") with { Name = "Other-alarm" });
|
||||
await svc.CreateScriptedAlarmAsync(UnsTreeTestDb.SeededEquipmentId, Sample("SA-MINE"), TestContext.Current.CancellationToken);
|
||||
await svc.CreateScriptedAlarmAsync("EQ-OTHER", Sample("SA-OTHER") with { Name = "Other-alarm" }, TestContext.Current.CancellationToken);
|
||||
|
||||
var rows = await svc.LoadAlarmsForEquipmentAsync(UnsTreeTestDb.SeededEquipmentId);
|
||||
var rows = await svc.LoadAlarmsForEquipmentAsync(UnsTreeTestDb.SeededEquipmentId, TestContext.Current.CancellationToken);
|
||||
|
||||
rows.Count.ShouldBe(1);
|
||||
rows[0].ScriptedAlarmId.ShouldBe("SA-MINE");
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed class UnsTreeServiceStructureTests
|
||||
{
|
||||
var service = SeededService();
|
||||
|
||||
var roots = await service.LoadStructureAsync();
|
||||
var roots = await service.LoadStructureAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var enterprise = roots.ShouldHaveSingleItem();
|
||||
enterprise.Kind.ShouldBe(UnsNodeKind.Enterprise);
|
||||
@@ -61,7 +61,7 @@ public sealed class UnsTreeServiceStructureTests
|
||||
{
|
||||
var service = SeededService();
|
||||
|
||||
var roots = await service.LoadStructureAsync();
|
||||
var roots = await service.LoadStructureAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var equipment = roots
|
||||
.Single()
|
||||
@@ -82,7 +82,7 @@ public sealed class UnsTreeServiceStructureTests
|
||||
{
|
||||
var service = SeededService();
|
||||
|
||||
var roots = await service.LoadStructureAsync();
|
||||
var roots = await service.LoadStructureAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var siteA = roots
|
||||
.Single()
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class UnsTreeServiceTagDriversTests
|
||||
|
||||
var service = new UnsTreeService(UnsTreeTestDb.Factory(dbName));
|
||||
|
||||
var drivers = await service.LoadTagDriversForEquipmentAsync("EQ-1");
|
||||
var drivers = await service.LoadTagDriversForEquipmentAsync("EQ-1", TestContext.Current.CancellationToken);
|
||||
|
||||
drivers.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public sealed class UnsTreeServiceTagTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedHierarchy(dbName);
|
||||
|
||||
var result = await service.CreateTagAsync("EQ-1", Input("TAG-1", "speed"));
|
||||
var result = await service.CreateTagAsync("EQ-1", Input("TAG-1", "speed"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe(TagAuthoringMovedError);
|
||||
@@ -89,9 +89,9 @@ public sealed class UnsTreeServiceTagTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedHierarchy(dbName);
|
||||
|
||||
(await service.CreateTagAsync("EQ-NOPE", Input("TAG-1", "speed"))).Error
|
||||
(await service.CreateTagAsync("EQ-NOPE", Input("TAG-1", "speed"), TestContext.Current.CancellationToken)).Error
|
||||
.ShouldBe(TagAuthoringMovedError);
|
||||
(await service.CreateTagAsync("EQ-1", Input("TAG-2", "speed", tagConfig: "{ not json"))).Error
|
||||
(await service.CreateTagAsync("EQ-1", Input("TAG-2", "speed", tagConfig: "{ not json"), TestContext.Current.CancellationToken)).Error
|
||||
.ShouldBe(TagAuthoringMovedError);
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ public sealed class UnsTreeServiceTagTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.UpdateTagAsync("TAG-1", Input("TAG-1", "renamed"), []);
|
||||
var result = await service.UpdateTagAsync("TAG-1", Input("TAG-1", "renamed"), [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe(TagAuthoringMovedError);
|
||||
@@ -130,7 +130,7 @@ public sealed class UnsTreeServiceTagTests
|
||||
rv = db.Tags.Single(t => t.TagId == "TAG-1").RowVersion;
|
||||
}
|
||||
|
||||
var result = await service.DeleteTagAsync("TAG-1", rv);
|
||||
var result = await service.DeleteTagAsync("TAG-1", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -145,7 +145,7 @@ public sealed class UnsTreeServiceTagTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.DeleteTagAsync("TAG-ghost", []);
|
||||
var result = await service.DeleteTagAsync("TAG-ghost", [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -160,6 +160,6 @@ public sealed class UnsTreeServiceTagTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedHierarchy(dbName);
|
||||
|
||||
(await service.LoadTagDriversForEquipmentAsync("EQ-1")).ShouldBeEmpty();
|
||||
(await service.LoadTagDriversForEquipmentAsync("EQ-1", TestContext.Current.CancellationToken)).ShouldBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedEquipmentAndScript(dbName);
|
||||
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"));
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -92,7 +92,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedEquipmentAndScript(dbName);
|
||||
|
||||
var result = await service.CreateVirtualTagAsync("EQ-NOPE", Input("VTAG-1", "computed"));
|
||||
var result = await service.CreateVirtualTagAsync("EQ-NOPE", Input("VTAG-1", "computed"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Equipment 'EQ-NOPE' not found.");
|
||||
@@ -109,7 +109,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
SeedEquipmentAndScript(dbName);
|
||||
|
||||
var result = await service.CreateVirtualTagAsync(
|
||||
"EQ-1", Input("VTAG-1", "computed", changeTriggered: false, timerIntervalMs: null));
|
||||
"EQ-1", Input("VTAG-1", "computed", changeTriggered: false, timerIntervalMs: null), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Pick at least one trigger — change or timer.");
|
||||
@@ -126,7 +126,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
SeedEquipmentAndScript(dbName);
|
||||
|
||||
var result = await service.CreateVirtualTagAsync(
|
||||
"EQ-1", Input("VTAG-1", "computed", changeTriggered: false, timerIntervalMs: 10));
|
||||
"EQ-1", Input("VTAG-1", "computed", changeTriggered: false, timerIntervalMs: 10), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("TimerIntervalMs must be at least 50 ms.");
|
||||
@@ -141,9 +141,9 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedEquipmentAndScript(dbName);
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"));
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "another"));
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "another"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("VirtualTag 'VTAG-1' already exists.");
|
||||
@@ -155,9 +155,9 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedEquipmentAndScript(dbName);
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"));
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"), TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-2", "computed"));
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-2", "computed"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("A virtual tag named 'computed' already exists on this equipment.");
|
||||
@@ -171,7 +171,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
SeedEquipmentAndScript(dbName);
|
||||
|
||||
var input = Input("VTAG-1", "computed", scriptId: "", changeTriggered: true);
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", input);
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", input, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -189,7 +189,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedEquipmentAndScript(dbName);
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"));
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"), TestContext.Current.CancellationToken);
|
||||
|
||||
byte[] rv;
|
||||
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
||||
@@ -200,7 +200,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
var updated = new VirtualTagInput("VTAG-1", "renamed", DataType: "Int32", ScriptId: "SCRIPT-1",
|
||||
ChangeTriggered: false, TimerIntervalMs: 250, Historize: true, Enabled: false);
|
||||
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-1", updated, rv);
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-1", updated, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -223,7 +223,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedEquipmentAndScript(dbName);
|
||||
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-nope", Input("VTAG-nope", "x"), []);
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-nope", Input("VTAG-nope", "x"), [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldBe("Row no longer exists.");
|
||||
@@ -235,8 +235,8 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedEquipmentAndScript(dbName);
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-A", "vt_a"));
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-B", "vt_b"));
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-A", "vt_a"), TestContext.Current.CancellationToken);
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-B", "vt_b"), TestContext.Current.CancellationToken);
|
||||
|
||||
byte[] rv;
|
||||
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
||||
@@ -245,7 +245,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
}
|
||||
|
||||
var input = Input("VTAG-A", "vt_b"); // try to rename to the name already used by VTAG-B
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-A", input, rv);
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-A", input, rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -260,7 +260,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
{
|
||||
var (service, dbName) = Fresh();
|
||||
SeedEquipmentAndScript(dbName);
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"));
|
||||
await service.CreateVirtualTagAsync("EQ-1", Input("VTAG-1", "computed"), TestContext.Current.CancellationToken);
|
||||
|
||||
byte[] rv;
|
||||
using (var db = UnsTreeTestDb.CreateNamed(dbName))
|
||||
@@ -268,7 +268,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
rv = db.VirtualTags.Single(v => v.VirtualTagId == "VTAG-1").RowVersion;
|
||||
}
|
||||
|
||||
var result = await service.DeleteVirtualTagAsync("VTAG-1", rv);
|
||||
var result = await service.DeleteVirtualTagAsync("VTAG-1", rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -283,7 +283,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
{
|
||||
var (service, _) = Fresh();
|
||||
|
||||
var result = await service.DeleteVirtualTagAsync("VTAG-ghost", []);
|
||||
var result = await service.DeleteVirtualTagAsync("VTAG-ghost", [], TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -298,7 +298,7 @@ public sealed class UnsTreeServiceVirtualTagTests
|
||||
var (service, dbName) = Fresh();
|
||||
SeedEquipmentAndScript(dbName);
|
||||
|
||||
var scripts = await service.LoadScriptsAsync();
|
||||
var scripts = await service.LoadScriptsAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
scripts.Count.ShouldBe(1);
|
||||
scripts[0].ScriptId.ShouldBe("SCRIPT-1");
|
||||
|
||||
+9
-9
@@ -104,7 +104,7 @@ public sealed class VirtualTagEquipTokenValidationTests
|
||||
var (service, dbName) = Fresh();
|
||||
Seed(dbName, EquipRefScript, referenceName: "Speed");
|
||||
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input());
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input(), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -117,7 +117,7 @@ public sealed class VirtualTagEquipTokenValidationTests
|
||||
var (service, dbName) = Fresh();
|
||||
Seed(dbName, EquipRefScript, referenceName: null);
|
||||
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input());
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input(), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -135,7 +135,7 @@ public sealed class VirtualTagEquipTokenValidationTests
|
||||
var (service, dbName) = Fresh();
|
||||
Seed(dbName, PlainScript, referenceName: null);
|
||||
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input());
|
||||
var result = await service.CreateVirtualTagAsync("EQ-1", Input(), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -151,7 +151,7 @@ public sealed class VirtualTagEquipTokenValidationTests
|
||||
Seed(dbName, EquipRefScript, referenceName: "Speed");
|
||||
var rv = SeedVirtualTagAndRowVersion(dbName);
|
||||
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-1", Input(name: "renamed"), rv);
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-1", Input(name: "renamed"), rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -165,7 +165,7 @@ public sealed class VirtualTagEquipTokenValidationTests
|
||||
Seed(dbName, EquipRefScript, referenceName: null);
|
||||
var rv = SeedVirtualTagAndRowVersion(dbName);
|
||||
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-1", Input(name: "renamed"), rv);
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-1", Input(name: "renamed"), rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -184,7 +184,7 @@ public sealed class VirtualTagEquipTokenValidationTests
|
||||
Seed(dbName, PlainScript, referenceName: null);
|
||||
var rv = SeedVirtualTagAndRowVersion(dbName);
|
||||
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-1", Input(name: "renamed"), rv);
|
||||
var result = await service.UpdateVirtualTagAsync("VTAG-1", Input(name: "renamed"), rv, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
@@ -203,7 +203,7 @@ public sealed class VirtualTagEquipTokenValidationTests
|
||||
var (service, dbName) = Fresh();
|
||||
Seed(dbName, EquipRefScript, referenceName: null);
|
||||
|
||||
var result = await service.CreateScriptedAlarmAsync("EQ-1", AlarmInput());
|
||||
var result = await service.CreateScriptedAlarmAsync("EQ-1", AlarmInput(), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -221,7 +221,7 @@ public sealed class VirtualTagEquipTokenValidationTests
|
||||
var (service, dbName) = Fresh();
|
||||
Seed(dbName, PlainScript, referenceName: "Speed"); // predicate has no token; only the template does
|
||||
|
||||
var result = await service.CreateScriptedAlarmAsync("EQ-1", AlarmInput(messageTemplate: "bad {{equip}}/Missing here"));
|
||||
var result = await service.CreateScriptedAlarmAsync("EQ-1", AlarmInput(messageTemplate: "bad {{equip}}/Missing here"), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Error.ShouldNotBeNull();
|
||||
@@ -235,7 +235,7 @@ public sealed class VirtualTagEquipTokenValidationTests
|
||||
var (service, dbName) = Fresh();
|
||||
Seed(dbName, EquipRefScript, referenceName: "Speed");
|
||||
|
||||
var result = await service.CreateScriptedAlarmAsync("EQ-1", AlarmInput());
|
||||
var result = await service.CreateScriptedAlarmAsync("EQ-1", AlarmInput(), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.Error.ShouldBeNull();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit"/>
|
||||
<PackageReference Include="xunit.v3" VersionOverride="3.2.2"/>
|
||||
<PackageReference Include="Shouldly"/>
|
||||
<PackageReference Include="bunit"/>
|
||||
<!-- bUnit (test-only) pulls AngleSharp 1.1.1 (GHSA-pgww-w46g-26qg, moderate, HTML parsing of untrusted
|
||||
@@ -21,10 +21,9 @@
|
||||
it directly, matching Configuration.Tests. -->
|
||||
<PackageReference Include="Microsoft.Data.SqlClient"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
|
||||
<!-- Akka.TestKit.Xunit2 ties this project to xunit v2 (TestKit isn't ported to v3 yet);
|
||||
used by the IAuditWriter adapter test to register a TestProbe in an ActorRegistry. -->
|
||||
<!-- Akka.TestKit.Xunit (xunit.v3 TestKit); used by the IAuditWriter adapter test to register a TestProbe in an ActorRegistry. -->
|
||||
<PackageReference Include="Akka.Hosting"/>
|
||||
<PackageReference Include="Akka.TestKit.Xunit2"/>
|
||||
<PackageReference Include="Akka.TestKit.Xunit"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
+14
-14
@@ -33,16 +33,16 @@ public sealed class AdminOperationsActorMailboxTests : ControlPlaneActorTestBase
|
||||
var connectClient = CreateTestProbe("connect-client");
|
||||
|
||||
connectClient.Send(actor, new TestDriverConnect("Blocking", "{}", TimeoutSeconds: 60, Guid.NewGuid()));
|
||||
probe.Started.Wait(PresenceBudget).ShouldBeTrue("the probe must be in flight before the ack is sent");
|
||||
probe.Started.Wait(PresenceBudget, TestContext.Current.CancellationToken).ShouldBeTrue("the probe must be in flight before the ack is sent");
|
||||
|
||||
actor.Tell(new AcknowledgeAlarmCommand("alarm-1", "op", null, CorrelationId.NewId()));
|
||||
var ack = ExpectMsg<AcknowledgeAlarmResult>(AckBudget);
|
||||
var ack = ExpectMsg<AcknowledgeAlarmResult>(AckBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
ack.Ok.ShouldBeTrue();
|
||||
|
||||
// Positive control: the probe reply still lands, with the right sender, once the probe returns.
|
||||
connectClient.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
connectClient.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken);
|
||||
probe.Release(new DriverProbeResult(true, null, TimeSpan.FromMilliseconds(5)));
|
||||
var result = connectClient.ExpectMsg<TestDriverConnectResult>(PresenceBudget);
|
||||
var result = connectClient.ExpectMsg<TestDriverConnectResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
result.Ok.ShouldBeTrue();
|
||||
result.LatencyMs.ShouldNotBeNull();
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public sealed class AdminOperationsActorMailboxTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new TestDriverConnect("Blocking", "{}", TimeoutSeconds: 1, Guid.NewGuid()));
|
||||
|
||||
var result = ExpectMsg<TestDriverConnectResult>(PresenceBudget);
|
||||
var result = ExpectMsg<TestDriverConnectResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
result.Ok.ShouldBeFalse();
|
||||
result.Message.ShouldNotBeNull();
|
||||
result.Message.ShouldContain("timed out");
|
||||
@@ -79,16 +79,16 @@ public sealed class AdminOperationsActorMailboxTests : ControlPlaneActorTestBase
|
||||
|
||||
gated.Hold();
|
||||
deployer.Send(actor, new StartDeployment("joe", CorrelationId.NewId()));
|
||||
gated.Entered.Wait(PresenceBudget).ShouldBeTrue("the compose must be parked before the ack is sent");
|
||||
gated.Entered.Wait(PresenceBudget, TestContext.Current.CancellationToken).ShouldBeTrue("the compose must be parked before the ack is sent");
|
||||
|
||||
actor.Tell(new AcknowledgeAlarmCommand("alarm-2", "op", null, CorrelationId.NewId()));
|
||||
ExpectMsg<AcknowledgeAlarmResult>(AckBudget).Ok.ShouldBeTrue();
|
||||
ExpectMsg<AcknowledgeAlarmResult>(AckBudget, cancellationToken: TestContext.Current.CancellationToken).Ok.ShouldBeTrue();
|
||||
|
||||
// Positive control: the deploy completes once released.
|
||||
deployer.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
deployer.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken);
|
||||
gated.Release();
|
||||
deployer.ExpectMsg<StartDeploymentResult>(PresenceBudget).Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget);
|
||||
deployer.ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -106,14 +106,14 @@ public sealed class AdminOperationsActorMailboxTests : ControlPlaneActorTestBase
|
||||
|
||||
gated.Hold();
|
||||
first.Send(actor, new StartDeployment("joe", CorrelationId.NewId()));
|
||||
gated.Entered.Wait(PresenceBudget).ShouldBeTrue();
|
||||
gated.Entered.Wait(PresenceBudget, TestContext.Current.CancellationToken).ShouldBeTrue();
|
||||
|
||||
second.Send(actor, new StartDeployment("kim", CorrelationId.NewId()));
|
||||
second.ExpectMsg<StartDeploymentResult>(AckBudget).Outcome.ShouldBe(StartDeploymentOutcome.AnotherDeploymentInFlight);
|
||||
second.ExpectMsg<StartDeploymentResult>(AckBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(StartDeploymentOutcome.AnotherDeploymentInFlight);
|
||||
|
||||
gated.Release();
|
||||
first.ExpectMsg<StartDeploymentResult>(PresenceBudget).Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget);
|
||||
first.ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
private sealed class BlockingProbe : IDriverProbe
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ public sealed class AdminOperationsActorServerFaultMessageTests : ControlPlaneAc
|
||||
|
||||
actor.Tell(new StartDeployment("joe", correlationId));
|
||||
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected);
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldNotContain("hunter2");
|
||||
@@ -46,7 +46,7 @@ public sealed class AdminOperationsActorServerFaultMessageTests : ControlPlaneAc
|
||||
|
||||
actor.Tell(new TestDriverConnect("Throwing", "{}", 5, correlationId));
|
||||
|
||||
var reply = ExpectMsg<TestDriverConnectResult>(PresenceBudget);
|
||||
var reply = ExpectMsg<TestDriverConnectResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Ok.ShouldBeFalse();
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldNotContain("hunter2");
|
||||
|
||||
+3
-3
@@ -67,9 +67,9 @@ public sealed class AdminOperationsActorSingleSnapshotTests : ControlPlaneActorT
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted, reply.Message);
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget);
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using (var verify = plain.CreateDbContext())
|
||||
{
|
||||
@@ -85,7 +85,7 @@ public sealed class AdminOperationsActorSingleSnapshotTests : ControlPlaneActorT
|
||||
|
||||
// Positive control: now that the row is visible to a fresh read, the gate rejects it.
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
var second = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
var second = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
second.Outcome.ShouldBe(StartDeploymentOutcome.Rejected);
|
||||
second.Message.ShouldNotBeNull();
|
||||
second.Message.ShouldContain("EquipmentIdNotDerived");
|
||||
|
||||
+8
-8
@@ -61,8 +61,8 @@ public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTe
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldContain("Intt16");
|
||||
@@ -80,8 +80,8 @@ public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTe
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected);
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldContain("TagConfigInvalid");
|
||||
@@ -102,8 +102,8 @@ public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTe
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
(reply.Message is null || !reply.Message.Contains("Intt16")).ShouldBeTrue();
|
||||
}
|
||||
@@ -121,8 +121,8 @@ public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTe
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget);
|
||||
ExpectMsg<StartDeploymentResult>(PresenceBudget).Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
coordinator.ExpectMsg<DispatchDeployment>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
}
|
||||
|
||||
// lmxopcua#781 item 3 — through the actor, in Error mode too: the writable mismatch is a warning, never a reject.
|
||||
|
||||
@@ -46,14 +46,14 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
var correlationId = CorrelationId.NewId();
|
||||
actor.Tell(new AcknowledgeAlarmCommand("alarm-42", "operator-jo", "looking into it", correlationId));
|
||||
|
||||
var published = topicProbe.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(3));
|
||||
var published = topicProbe.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
published.AlarmId.ShouldBe("alarm-42");
|
||||
published.Operation.ShouldBe("Acknowledge");
|
||||
published.User.ShouldBe("operator-jo");
|
||||
published.Comment.ShouldBe("looking into it");
|
||||
published.UnshelveAtUtc.ShouldBeNull();
|
||||
|
||||
var reply = ExpectMsg<AcknowledgeAlarmResult>(TimeSpan.FromSeconds(3));
|
||||
var reply = ExpectMsg<AcknowledgeAlarmResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Ok.ShouldBeTrue();
|
||||
reply.Message.ShouldBeNull();
|
||||
reply.CorrelationId.ShouldBe(correlationId);
|
||||
@@ -72,13 +72,13 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
var correlationId = CorrelationId.NewId();
|
||||
actor.Tell(new ShelveAlarmCommand("alarm-7", "op-kim", ShelveKind.OneShot, UnshelveAtUtc: null, Comment: null, correlationId));
|
||||
|
||||
var published = topicProbe.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(3));
|
||||
var published = topicProbe.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
published.AlarmId.ShouldBe("alarm-7");
|
||||
published.Operation.ShouldBe("OneShotShelve");
|
||||
published.User.ShouldBe("op-kim");
|
||||
published.UnshelveAtUtc.ShouldBeNull();
|
||||
|
||||
var reply = ExpectMsg<ShelveAlarmResult>(TimeSpan.FromSeconds(3));
|
||||
var reply = ExpectMsg<ShelveAlarmResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Ok.ShouldBeTrue();
|
||||
reply.CorrelationId.ShouldBe(correlationId);
|
||||
}
|
||||
@@ -96,13 +96,13 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
var unshelveAt = DateTime.UtcNow.AddMinutes(15);
|
||||
actor.Tell(new ShelveAlarmCommand("alarm-9", "op-lee", ShelveKind.Timed, unshelveAt, Comment: "maint window", CorrelationId.NewId()));
|
||||
|
||||
var published = topicProbe.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(3));
|
||||
var published = topicProbe.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
published.AlarmId.ShouldBe("alarm-9");
|
||||
published.Operation.ShouldBe("TimedShelve");
|
||||
published.UnshelveAtUtc.ShouldBe(unshelveAt);
|
||||
published.Comment.ShouldBe("maint window");
|
||||
|
||||
var reply = ExpectMsg<ShelveAlarmResult>(TimeSpan.FromSeconds(3));
|
||||
var reply = ExpectMsg<ShelveAlarmResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
@@ -118,11 +118,11 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new ShelveAlarmCommand("alarm-3", "op-sam", ShelveKind.Unshelve, UnshelveAtUtc: null, Comment: null, CorrelationId.NewId()));
|
||||
|
||||
var published = topicProbe.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(3));
|
||||
var published = topicProbe.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
published.Operation.ShouldBe("Unshelve");
|
||||
published.UnshelveAtUtc.ShouldBeNull();
|
||||
|
||||
ExpectMsg<ShelveAlarmResult>(TimeSpan.FromSeconds(3)).Ok.ShouldBeTrue();
|
||||
ExpectMsg<ShelveAlarmResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken).Ok.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>Verifies a <see cref="ShelveKind.Timed"/> shelve with a null UnshelveAtUtc is rejected
|
||||
@@ -137,13 +137,13 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new ShelveAlarmCommand("alarm-1", "op-zoe", ShelveKind.Timed, UnshelveAtUtc: null, Comment: null, CorrelationId.NewId()));
|
||||
|
||||
var reply = ExpectMsg<ShelveAlarmResult>(TimeSpan.FromSeconds(3));
|
||||
var reply = ExpectMsg<ShelveAlarmResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Ok.ShouldBeFalse();
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldContain("UnshelveAtUtc");
|
||||
|
||||
// No command should have been published.
|
||||
topicProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
topicProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that starting a deployment inserts a row and dispatches to the coordinator.</summary>
|
||||
@@ -156,11 +156,11 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
var dispatch = coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
|
||||
var dispatch = coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
dispatch.DeploymentId.Value.ShouldNotBe(Guid.Empty);
|
||||
dispatch.RevisionHash.Value.Length.ShouldBe(64);
|
||||
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
reply.DeploymentId.ShouldBe(dispatch.DeploymentId);
|
||||
reply.RevisionHash.ShouldBe(dispatch.RevisionHash);
|
||||
@@ -237,8 +237,8 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected);
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldContain("UnsEffectiveNameCollision"); // the v3 rule's error code
|
||||
@@ -275,8 +275,8 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected);
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldContain("EquipmentIdNotDerived");
|
||||
@@ -321,9 +321,9 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
// Still dispatches — the advisory is non-blocking.
|
||||
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
|
||||
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldContain("will compile");
|
||||
@@ -360,9 +360,9 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
|
||||
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
// No compiled scripts → no advisory emitted.
|
||||
(reply.Message is null || !reply.Message.Contains("will compile")).ShouldBeTrue();
|
||||
@@ -396,9 +396,9 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
|
||||
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldContain("will compile");
|
||||
@@ -454,8 +454,8 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected);
|
||||
reply.Message.ShouldNotBeNull();
|
||||
reply.Message.ShouldContain("ClusterEnabledNodeCountMismatch");
|
||||
@@ -511,9 +511,9 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
|
||||
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
(reply.Message is null || !reply.Message.Contains("ClusterEnabledNodeCountMismatch")).ShouldBeTrue();
|
||||
(reply.Message is null || !reply.Message.Contains("ClusterRedundancyModeInvalid")).ShouldBeTrue();
|
||||
@@ -544,8 +544,8 @@ public sealed class AdminOperationsActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
reply.Outcome.ShouldBe(StartDeploymentOutcome.AnotherDeploymentInFlight);
|
||||
reply.DeploymentId.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
+4
-4
@@ -35,7 +35,7 @@ public sealed class AuditWriterActorRejectedRowTests : ControlPlaneActorTestBase
|
||||
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance); // PostStop flushes
|
||||
ExpectTerminated(actor);
|
||||
ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var db = rejecting.CreateDbContext();
|
||||
var persisted = db.ConfigAuditLogs.Select(r => r.EventId).ToList();
|
||||
@@ -71,7 +71,7 @@ public sealed class AuditWriterActorRejectedRowTests : ControlPlaneActorTestBase
|
||||
foreach (var id in ids) actor.Tell(NewEvent(id));
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance);
|
||||
ExpectTerminated(actor);
|
||||
ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var db = factory.CreateDbContext();
|
||||
var persisted = db.ConfigAuditLogs.Select(r => r.EventId).ToList();
|
||||
@@ -99,7 +99,7 @@ public sealed class AuditWriterActorRejectedRowTests : ControlPlaneActorTestBase
|
||||
foreach (var id in ids) actor.Tell(NewEvent(id));
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance);
|
||||
ExpectTerminated(actor, PresenceBudget);
|
||||
ExpectTerminated(actor, PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var db = sql.NewContext();
|
||||
var persisted = db.ConfigAuditLogs.Select(r => r.EventId).ToList();
|
||||
@@ -120,7 +120,7 @@ public sealed class AuditWriterActorRejectedRowTests : ControlPlaneActorTestBase
|
||||
for (var i = 0; i < 5; i++) actor.Tell(NewEvent(Guid.NewGuid()));
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance);
|
||||
ExpectTerminated(actor); // terminates ⇒ the retry loop did not spin
|
||||
ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken); // terminates ⇒ the retry loop did not spin
|
||||
|
||||
using var db = rejecting.CreateDbContext();
|
||||
db.ConfigAuditLogs.Count().ShouldBe(0);
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.ConfigAuditLogs.Count().ShouldBe(AuditWriterActor.FlushBatchSize);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that duplicate event IDs within a batch are deduplicated in the buffer.</summary>
|
||||
@@ -57,7 +57,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase
|
||||
// Force a flush — send PoisonPill, which triggers PostStop → FlushBuffer.
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance);
|
||||
ExpectTerminated(actor);
|
||||
ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.ConfigAuditLogs.Count().ShouldBe(100, "in-buffer dedup should collapse duplicate EventIds");
|
||||
@@ -76,7 +76,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase
|
||||
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance);
|
||||
ExpectTerminated(actor);
|
||||
ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.ConfigAuditLogs.Count().ShouldBe(10);
|
||||
@@ -94,7 +94,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase
|
||||
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance);
|
||||
ExpectTerminated(actor);
|
||||
ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
var row = db.ConfigAuditLogs.Single();
|
||||
@@ -127,7 +127,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase
|
||||
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance);
|
||||
ExpectTerminated(actor);
|
||||
ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
var row = db.ConfigAuditLogs.Single();
|
||||
@@ -146,7 +146,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase
|
||||
var testRef = ActorOfAsTestActorRef<AuditWriterActor>(AuditWriterActor.Props(dbFactory));
|
||||
IAuditWriter writer = testRef.UnderlyingActor;
|
||||
|
||||
var task = writer.WriteAsync(NewEvent(Guid.NewGuid(), action: "Published"));
|
||||
var task = writer.WriteAsync(NewEvent(Guid.NewGuid(), action: "Published"), TestContext.Current.CancellationToken);
|
||||
task.IsCompletedSuccessfully.ShouldBeTrue("WriteAsync must be best-effort and complete synchronously");
|
||||
await Should.NotThrowAsync(async () => await task);
|
||||
}
|
||||
@@ -165,7 +165,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase
|
||||
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance);
|
||||
ExpectTerminated(actor);
|
||||
ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
var row = db.ConfigAuditLogs.Single();
|
||||
@@ -187,7 +187,7 @@ public sealed class AuditWriterActorTests : ControlPlaneActorTestBase
|
||||
|
||||
Watch(actor);
|
||||
actor.Tell(PoisonPill.Instance);
|
||||
ExpectTerminated(actor);
|
||||
ExpectTerminated(actor, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
var row = db.ConfigAuditLogs.Single();
|
||||
|
||||
+6
-6
@@ -28,7 +28,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe
|
||||
SeedNode(dbFactory, "site-a-1:4053", enabled: true);
|
||||
|
||||
EventFilter.Warning(contains: "site-a-1:4053").ExpectOne(TimeSpan.FromSeconds(10), () =>
|
||||
Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory)));
|
||||
Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory)), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -49,7 +49,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe
|
||||
dbFactory, sweepInterval: TimeSpan.FromMilliseconds(400)));
|
||||
// Hold the filter open past several sweeps rather than returning immediately.
|
||||
ExpectNoMsg(TimeSpan.FromSeconds(2.5));
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -67,7 +67,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe
|
||||
Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(
|
||||
dbFactory, sweepInterval: TimeSpan.FromMilliseconds(400)));
|
||||
ExpectNoMsg(TimeSpan.FromSeconds(1.5));
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -88,7 +88,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe
|
||||
Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(
|
||||
dbFactory, ownClusterId: "MAIN", sweepInterval: TimeSpan.FromMilliseconds(400)));
|
||||
ExpectNoMsg(TimeSpan.FromSeconds(1.5));
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -103,7 +103,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe
|
||||
SeedNode(dbFactory, "main-1:4053", enabled: true, clusterId: "MAIN");
|
||||
|
||||
EventFilter.Warning(contains: "main-1:4053").ExpectOne(TimeSpan.FromSeconds(10), () =>
|
||||
Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory, ownClusterId: "MAIN")));
|
||||
Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory, ownClusterId: "MAIN")), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -118,7 +118,7 @@ public sealed class ClusterNodeAddressReconcilerActorTests : ControlPlaneActorTe
|
||||
SeedNode(dbFactory, "site-a-1:4053", enabled: true, clusterId: "SITE-A");
|
||||
|
||||
EventFilter.Warning(contains: "site-a-1:4053").ExpectOne(TimeSpan.FromSeconds(10), () =>
|
||||
Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory, ownClusterId: null)));
|
||||
Sys.ActorOf(ClusterNodeAddressReconcilerActor.Props(dbFactory, ownClusterId: null)), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
private static void SeedNode(
|
||||
|
||||
+54
-54
@@ -3,7 +3,7 @@ using Akka.Actor;
|
||||
using Akka.Cluster.Tools.PublishSubscribe;
|
||||
using Akka.Event;
|
||||
using Akka.TestKit;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
@@ -86,7 +86,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
// without an explicit sender would ack to TestActor and this wait would hang.
|
||||
DistributedPubSub.Get(Sys).Mediator.Tell(
|
||||
new Subscribe(DeploymentsTopic, mediatorProbe.Ref), mediatorProbe.Ref);
|
||||
mediatorProbe.ExpectMsg<SubscribeAck>(TimeSpan.FromSeconds(5));
|
||||
mediatorProbe.ExpectMsg<SubscribeAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var actor = Spawn(factory, new MeshTransportOptions());
|
||||
var dispatch = new DispatchDeployment(
|
||||
@@ -94,7 +94,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(new MeshCommand(DeploymentsTopic, dispatch));
|
||||
|
||||
mediatorProbe.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(5)).ShouldBe(dispatch);
|
||||
mediatorProbe.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(dispatch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -114,7 +114,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(ack, node.Ref);
|
||||
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).ShouldBe(ack);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(ack);
|
||||
// Forward, not Tell: the coordinator must see the node, not this relay.
|
||||
coordinator.LastSender.ShouldBe(node.Ref);
|
||||
}
|
||||
@@ -133,7 +133,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
new CorrelationId(Guid.NewGuid()));
|
||||
|
||||
EventFilter.Warning(contains: "not resolvable").ExpectOne(TimeSpan.FromSeconds(5), () =>
|
||||
actor.Tell(ack));
|
||||
actor.Tell(ack), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -147,11 +147,11 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
// fail with two matches instead of one.
|
||||
EventFilter.Warning(contains: "contact set was NOT refreshed").ExpectOne(
|
||||
TimeSpan.FromSeconds(5),
|
||||
() => actor.Tell(new Status.Failure(new InvalidOperationException("db down"))));
|
||||
() => actor.Tell(new Status.Failure(new InvalidOperationException("db down"))), TestContext.Current.CancellationToken);
|
||||
|
||||
// Still serving: a transient DB outage must not take the command boundary down with it.
|
||||
actor.Tell(new MeshCommand(DeploymentsTopic, "anything"));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ Mode = Grpc
|
||||
@@ -177,7 +177,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 3, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 3, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
rig.Dialer.Calls.Select(c => c.Endpoint).ShouldBe(
|
||||
new[] { "http://host-a:4056", "http://host-b:4056", "http://host-c:4056" }, ignoreOrder: true);
|
||||
|
||||
@@ -198,15 +198,15 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
var mediatorProbe = CreateTestProbe();
|
||||
DistributedPubSub.Get(Sys).Mediator.Tell(
|
||||
new Subscribe(DeploymentsTopic, mediatorProbe.Ref), mediatorProbe.Ref);
|
||||
mediatorProbe.ExpectMsg<SubscribeAck>(TimeSpan.FromSeconds(5));
|
||||
mediatorProbe.ExpectMsg<SubscribeAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var rig = SpawnGrpcRig(factory);
|
||||
AwaitContactsLoaded(rig);
|
||||
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5));
|
||||
mediatorProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
mediatorProbe.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -226,11 +226,11 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
rig.Dialer.Calls[0].Endpoint.ShouldContain("host-a");
|
||||
|
||||
// And no late arrival from the two excluded rows.
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
rig.Dialer.Calls.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
@@ -248,13 +248,13 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
GrpcRig? rig = null;
|
||||
EventFilter.Warning(contains: "has no GrpcPort").ExpectOne(
|
||||
TimeSpan.FromSeconds(5), () => rig = SpawnGrpcRig(factory));
|
||||
TimeSpan.FromSeconds(5), () => rig = SpawnGrpcRig(factory), TestContext.Current.CancellationToken);
|
||||
|
||||
rig.ShouldNotBeNull();
|
||||
AwaitContactsLoaded(rig);
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
rig.Dialer.Calls[0].Endpoint.ShouldBe("http://host-b:4056");
|
||||
}
|
||||
|
||||
@@ -273,11 +273,11 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
SetGrpcPort(factory, "host-a:4053", 4056);
|
||||
rig.Actor.Tell(new CentralCommunicationActor.RefreshContacts());
|
||||
AwaitCondition(
|
||||
() => rig.Psk.KnownClusterPublications.Count >= 2, TimeSpan.FromSeconds(5));
|
||||
() => rig.Psk.KnownClusterPublications.Count >= 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
SetGrpcPort(factory, "host-a:4053", null);
|
||||
EventFilter.Warning(contains: "has no GrpcPort").ExpectOne(TimeSpan.FromSeconds(5), () =>
|
||||
rig.Actor.Tell(new CentralCommunicationActor.RefreshContacts()));
|
||||
rig.Actor.Tell(new CentralCommunicationActor.RefreshContacts()), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -302,14 +302,14 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
// The node id, not the endpoint: an operator reading the log is looking at ClusterNode rows.
|
||||
EventFilter.Warning(contains: "host-a:4053").ExpectOne(TimeSpan.FromSeconds(5), () =>
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())));
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken);
|
||||
|
||||
// Both were attempted; the healthy one is unaffected by its neighbour's failure.
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
// Still serving after a per-node failure — no supervision restart, no state loss.
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 4, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 4, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -330,9 +330,9 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
AwaitContactsLoaded(rig);
|
||||
|
||||
EventFilter.Warning(contains: "Unauthenticated").ExpectOne(TimeSpan.FromSeconds(5), () =>
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())));
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken);
|
||||
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -343,7 +343,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
AwaitContactsLoaded(rig);
|
||||
|
||||
EventFilter.Warning(contains: "dropping").ExpectOne(TimeSpan.FromSeconds(5), () =>
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())));
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken);
|
||||
|
||||
rig.Dialer.Calls.ShouldBeEmpty();
|
||||
}
|
||||
@@ -359,13 +359,13 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
AwaitContactsLoaded(rig);
|
||||
|
||||
EventFilter.Error(contains: "Cannot encode").ExpectOne(TimeSpan.FromSeconds(5), () =>
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, "not a command record")));
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, "not a command record")), TestContext.Current.CancellationToken);
|
||||
|
||||
rig.Dialer.Calls.ShouldBeEmpty();
|
||||
|
||||
// Still serving.
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -384,7 +384,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, new DispatchDeployment(
|
||||
new DeploymentId(Guid.NewGuid()), new RevisionHash("abc"), correlationId)));
|
||||
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
rig.Dialer.Calls.ShouldAllBe(c => c.Envelope.CorrelationId == correlationId.ToString());
|
||||
// One envelope object, encoded once and fanned — not re-encoded per node.
|
||||
rig.Dialer.Calls.Select(c => c.Envelope).Distinct().Count().ShouldBe(1);
|
||||
@@ -408,7 +408,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
rig.Actor.Tell(new MeshCommand(
|
||||
"alarms", new AlarmCommand("alarm-1", "Acknowledge", "operator", null, null)));
|
||||
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
rig.Dialer.Calls.Select(c => c.Envelope.CorrelationId).Distinct().Count().ShouldBe(1);
|
||||
rig.Dialer.Calls[0].Envelope.CorrelationId.ShouldNotBeNullOrWhiteSpace();
|
||||
rig.Dialer.Calls[0].Envelope.AlarmCommand.AlarmId.ShouldBe("alarm-1");
|
||||
@@ -461,7 +461,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
new CorrelationId(Guid.NewGuid()));
|
||||
rig.Actor.Tell(ack);
|
||||
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).ShouldBe(ack);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(ack);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -502,11 +502,11 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
rig.Actor.Tell(new CentralCommunicationActor.RefreshContacts());
|
||||
AwaitCondition(
|
||||
() => rig.Psk.KnownClusterPublications.Count >= 2, TimeSpan.FromSeconds(5));
|
||||
() => rig.Psk.KnownClusterPublications.Count >= 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
rig.Dialer.Calls.Select(c => c.Endpoint).ShouldBe(
|
||||
new[] { "http://host-a:4056", "http://host-b:4056" }, ignoreOrder: true);
|
||||
|
||||
@@ -540,7 +540,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
() => psk));
|
||||
|
||||
AwaitCondition(
|
||||
() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(5));
|
||||
() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
psk.KnownClusterPublications[0].ShouldBe(new[] { "C1" });
|
||||
psk.InvalidateAllCount.ShouldBeGreaterThanOrEqualTo(1);
|
||||
}
|
||||
@@ -565,8 +565,8 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
rig.Dialer.Calls.Count.ShouldBe(1);
|
||||
rig.Dialer.Calls[0].ClusterId.ShouldBe("C1");
|
||||
}
|
||||
@@ -589,7 +589,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
// Targets-before-gate, observed the only way it can be from outside the actor: the very next
|
||||
// message finds the dispatch set already populated.
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 1, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -614,7 +614,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
// same absence at two severities.
|
||||
EventFilter.Error(contains: "no command dialer is wired").ExpectOne(
|
||||
TimeSpan.FromSeconds(5),
|
||||
() => actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())));
|
||||
() => actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ PreStart resolve-race logging
|
||||
@@ -673,7 +673,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
actor!.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
return dialer.Calls.Count >= 1;
|
||||
}, TimeSpan.FromSeconds(5));
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
// Both retries actually happened — this fact is not vacuously true because the factories
|
||||
// never got a second chance to run.
|
||||
@@ -731,7 +731,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
Sys.ActorOf(CentralCommunicationActor.Props(
|
||||
factory, ModeOptions(mode), () => null, () => new RecordingDialer(), PskFactory));
|
||||
AwaitCondition(() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(10));
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -744,9 +744,9 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
EventFilter.Warning().Expect(2, TimeSpan.FromSeconds(10), () =>
|
||||
Sys.ActorOf(CentralCommunicationActor.Props(
|
||||
factory, GrpcMode(), () => null, () => new RecordingDialer(), () => psk)));
|
||||
factory, GrpcMode(), () => null, () => new RecordingDialer(), () => psk)), TestContext.Current.CancellationToken);
|
||||
|
||||
AwaitCondition(() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(10));
|
||||
AwaitCondition(() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -778,25 +778,25 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
// A mailbox barrier also waits for PreStart to register the zero-delay periodic tick.
|
||||
var marker = new object();
|
||||
actor.Tell(marker);
|
||||
barrier.ExpectMsg<UnhandledMessage>(TimeSpan.FromSeconds(5)).Message.ShouldBeSameAs(marker);
|
||||
barrier.ExpectMsg<UnhandledMessage>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Message.ShouldBeSameAs(marker);
|
||||
scheduler.Advance(TimeSpan.Zero);
|
||||
loads.ExpectMsg(1, TimeSpan.FromSeconds(5));
|
||||
local.AwaitCondition(() => Volatile.Read(ref calls) == 2, TimeSpan.FromSeconds(5));
|
||||
loads.ExpectMsg(1, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
local.AwaitCondition(() => Volatile.Read(ref calls) == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
// This mailbox marker is handled after the failed ContactsLoaded handler returns.
|
||||
// Unlike observing the provider call alone, it proves retry registration is complete.
|
||||
actor.Tell(marker);
|
||||
barrier.ExpectMsg<UnhandledMessage>(TimeSpan.FromSeconds(5)).Message.ShouldBeSameAs(marker);
|
||||
barrier.ExpectMsg<UnhandledMessage>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Message.ShouldBeSameAs(marker);
|
||||
|
||||
local.EventFilter.Info(contains: "Known-cluster gate published").ExpectOne(
|
||||
TimeSpan.FromSeconds(5), () => actor.Tell(new CentralCommunicationActor.RefreshContacts()));
|
||||
loads.ExpectMsg(2, TimeSpan.FromSeconds(5));
|
||||
TimeSpan.FromSeconds(5), () => actor.Tell(new CentralCommunicationActor.RefreshContacts()), TestContext.Current.CancellationToken);
|
||||
loads.ExpectMsg(2, TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Cross the pending retry deadline, leaving the 60-second periodic tick untouched.
|
||||
// LoadContactsFromDb uses Task.Run, so observe its boundary for the original absence
|
||||
// budget rather than asserting before a mistakenly retained retry can start its read.
|
||||
scheduler.Advance(TimeSpan.FromMilliseconds(2500));
|
||||
loads.ExpectNoMsg(TimeSpan.FromMilliseconds(2500));
|
||||
loads.ExpectNoMsg(TimeSpan.FromMilliseconds(2500), TestContext.Current.CancellationToken);
|
||||
factory.Calls.ShouldBe(2);
|
||||
psk.KnownClusterPublications.Count.ShouldBe(1);
|
||||
}
|
||||
@@ -810,7 +810,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
factory, GrpcMode(), () => null, () => new RecordingDialer(), () => new RecordingPskProvider(),
|
||||
bootstrapRetryInterval: TimeSpan.FromMilliseconds(20)));
|
||||
|
||||
EventFilter.Warning().Expect(32, TimeSpan.FromSeconds(10), () => { });
|
||||
EventFilter.Warning().Expect(32, TimeSpan.FromSeconds(10), () => { }, TestContext.Current.CancellationToken);
|
||||
Thread.Sleep(1500);
|
||||
// 1 initial + 15 retries; the 60 s periodic tick has not fired again.
|
||||
factory.Calls.ShouldBe(16);
|
||||
@@ -854,7 +854,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
// ran and PublishKnownClusters's property access performed the first-use retry — not
|
||||
// merely that the actor was constructed.
|
||||
AwaitCondition(() => pskCalls >= 2, TimeSpan.FromSeconds(5));
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
// The bootstrap retry is BOUNDED: with a 50 ms interval and a budget of 15 it has long
|
||||
// finished, so the call count plateaus instead of growing with elapsed time.
|
||||
@@ -873,7 +873,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
{
|
||||
actor.Tell(new CentralCommunicationActor.RefreshContacts());
|
||||
AwaitCondition(() => pskCalls > pskCallsAfterFirstLoad, TimeSpan.FromSeconds(5));
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -904,15 +904,15 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
// Proof PreStart's resolve attempt and the first contact load it schedules have
|
||||
// actually run, so this block's silence is not vacuous.
|
||||
AwaitCondition(() => psk.KnownClusterPublications.Count >= 1, TimeSpan.FromSeconds(5));
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
EventFilter.Error(contains: "no command dialer is wired").ExpectOne(TimeSpan.FromSeconds(5), () =>
|
||||
actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())));
|
||||
actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken);
|
||||
|
||||
// A SECOND dispatch must ALSO log it: this signal is per-dispatch and unguarded, the
|
||||
// opposite shape from the PSK provider's once-per-incarnation guard above.
|
||||
EventFilter.Error(contains: "no command dialer is wired").ExpectOne(TimeSpan.FromSeconds(5), () =>
|
||||
actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())));
|
||||
actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch())), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
private static void SetGrpcPort(
|
||||
@@ -943,7 +943,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
AwaitContactsLoaded(rig);
|
||||
// The dialer is resolved lazily on first use; a dispatch forces that, and the NEXT refresh prunes.
|
||||
rig.Actor.Tell(new MeshCommand(DeploymentsTopic, NewDispatch()));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5));
|
||||
AwaitCondition(() => rig.Dialer.Calls.Count == 2, TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
using (var db = factory.CreateDbContext())
|
||||
{
|
||||
@@ -954,7 +954,7 @@ public class CentralCommunicationActorTests : ControlPlaneActorTestBase
|
||||
|
||||
AwaitCondition(
|
||||
() => rig.Dialer.RetainCalls.Any(set => set.Count == 1 && set.Single().Contains("host-a")),
|
||||
TimeSpan.FromSeconds(5));
|
||||
TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
rig.Dialer.RetainCalls.Last().ShouldBe(new[] { "http://host-a:4056" });
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -75,7 +75,7 @@ public sealed class ControlPlaneStarterOrderTests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await host.StopAsync();
|
||||
await host.StopAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,16 +101,16 @@ public sealed class ControlPlaneStarterOrderTests
|
||||
// StartAsync at which a synchronous assertion is correct.
|
||||
using var host = BuildAdminHost(mode, useSignallingProvider: true);
|
||||
|
||||
await host.StartAsync();
|
||||
await host.StartAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
var signal = (SignallingPskProvider)host.Services.GetRequiredService<IMeshPskProvider>();
|
||||
await signal.Published.WaitAsync(TimeSpan.FromSeconds(20));
|
||||
await signal.Published.WaitAsync(TimeSpan.FromSeconds(20), TestContext.Current.CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await host.StopAsync();
|
||||
await host.StopAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ public sealed class MeshPskProviderRefreshTests
|
||||
resolver.Value = "new";
|
||||
resolver.Hold();
|
||||
var refresh = provider.RefreshAsync(new[] { "SITE-A" }, CancellationToken.None);
|
||||
resolver.Entered.Wait(TimeSpan.FromSeconds(5)).ShouldBeTrue();
|
||||
resolver.Entered.Wait(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken).ShouldBeTrue();
|
||||
|
||||
// Mid-refresh: the old key, from memory — the store is NOT re-read on this path.
|
||||
var callsMidRefresh = resolver.Calls;
|
||||
|
||||
+1
-1
@@ -307,7 +307,7 @@ public sealed class MeshPskProviderTests
|
||||
return psk;
|
||||
}, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default)).ToArray();
|
||||
|
||||
arrived.Wait(TimeSpan.FromSeconds(10)).ShouldBeTrue("all callers should reach the start gate");
|
||||
arrived.Wait(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken).ShouldBeTrue("all callers should reach the start gate");
|
||||
start.Set();
|
||||
var results = await Task.WhenAll(callers);
|
||||
|
||||
|
||||
+7
-7
@@ -38,7 +38,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase
|
||||
coordinator.Tell(new Commons.Messages.Deploy.DispatchDeployment(
|
||||
deploymentId, new RevisionHash("rev-1"), CorrelationId.NewId()));
|
||||
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget);
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
cmd.Topic.ShouldBe(DeploymentsTopic);
|
||||
cmd.Message.ShouldBeOfType<Commons.Messages.Deploy.DispatchDeployment>();
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase
|
||||
var subscriber = CreateTestProbe();
|
||||
DistributedPubSub.Get(Sys).Mediator.Tell(
|
||||
new Subscribe(DeploymentsTopic, subscriber.Ref), subscriber.Ref);
|
||||
subscriber.ExpectMsg<SubscribeAck>(PresenceBudget);
|
||||
subscriber.ExpectMsg<SubscribeAck>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var coordinator = Sys.ActorOf(ConfigPublishCoordinator.Props(NewInMemoryDbFactory()));
|
||||
|
||||
@@ -59,7 +59,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase
|
||||
|
||||
// Presence budget, not 5 s: the coordinator's first ConfigDb (EF InMemory) read sits in front
|
||||
// of the dispatch and is a cold multi-second cost on a loaded CI runner (#606).
|
||||
subscriber.ExpectMsg<Commons.Messages.Deploy.DispatchDeployment>(PresenceBudget);
|
||||
subscriber.ExpectMsg<Commons.Messages.Deploy.DispatchDeployment>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -75,7 +75,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase
|
||||
|
||||
admin.Tell(new RestartDriver("C1", "driver-1", "alice", Guid.NewGuid()));
|
||||
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget);
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
cmd.Topic.ShouldBe(DriverControlTopic.Name);
|
||||
cmd.Message.ShouldBeOfType<RestartDriver>();
|
||||
}
|
||||
@@ -93,7 +93,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase
|
||||
|
||||
admin.Tell(new ReconnectDriver("C1", "driver-1", "alice", Guid.NewGuid()));
|
||||
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget);
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
cmd.Topic.ShouldBe(DriverControlTopic.Name);
|
||||
cmd.Message.ShouldBeOfType<ReconnectDriver>();
|
||||
}
|
||||
@@ -111,7 +111,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase
|
||||
|
||||
admin.Tell(new AcknowledgeAlarmCommand("alarm-1", "alice", "c", CorrelationId.NewId()));
|
||||
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget);
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
cmd.Topic.ShouldBe(AlarmCommandsTopic.Name);
|
||||
cmd.Message.ShouldBeOfType<AlarmCommand>();
|
||||
}
|
||||
@@ -129,7 +129,7 @@ public class MeshRouterDispatchTests : ControlPlaneActorTestBase
|
||||
|
||||
admin.Tell(new ShelveAlarmCommand("alarm-1", "alice", ShelveKind.OneShot, null, "c", CorrelationId.NewId()));
|
||||
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget);
|
||||
var cmd = router.ExpectMsg<MeshCommand>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
cmd.Topic.ShouldBe(AlarmCommandsTopic.Name);
|
||||
cmd.Message.ShouldBeOfType<AlarmCommand>();
|
||||
}
|
||||
|
||||
+3
-3
@@ -32,20 +32,20 @@ public sealed class ConfigComposerSqlServerHashStabilityTests
|
||||
// Collation order a-2, B-1, c-3; ordinal order B-1, a-2, c-3.
|
||||
foreach (var id in new[] { "c-3", "B-1", "a-2" })
|
||||
seed.Scripts.Add(new Script { ScriptId = id, Name = id, SourceCode = "return 1;", SourceHash = id });
|
||||
await seed.SaveChangesAsync();
|
||||
await seed.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
await using var legacyDb = sql.NewContext();
|
||||
var legacy = await LegacySnapshotAndFlattenAsync(legacyDb);
|
||||
|
||||
await using var viaDb = sql.NewContext();
|
||||
var current = await ConfigComposer.SnapshotAndFlattenAsync(viaDb);
|
||||
var current = await ConfigComposer.SnapshotAndFlattenAsync(viaDb, TestContext.Current.CancellationToken);
|
||||
current.Blob.ShouldBe(legacy.Blob);
|
||||
current.RevisionHash.ShouldBe(legacy.RevisionHash);
|
||||
|
||||
// The deploy path's form (validate + compose from ONE snapshot) must agree too.
|
||||
await using var deployDb = sql.NewContext();
|
||||
var viaSnapshot = ConfigComposer.Compose(await DraftSnapshotFactory.FromConfigDbAsync(deployDb));
|
||||
var viaSnapshot = ConfigComposer.Compose(await DraftSnapshotFactory.FromConfigDbAsync(deployDb, TestContext.Current.CancellationToken));
|
||||
viaSnapshot.RevisionHash.ShouldBe(legacy.RevisionHash);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
var f = NewInMemoryDbFactory();
|
||||
|
||||
await using var db1 = f.CreateDbContext();
|
||||
var a1 = await ConfigComposer.SnapshotAndFlattenAsync(db1);
|
||||
var a1 = await ConfigComposer.SnapshotAndFlattenAsync(db1, TestContext.Current.CancellationToken);
|
||||
|
||||
await using var db2 = f.CreateDbContext();
|
||||
var a2 = await ConfigComposer.SnapshotAndFlattenAsync(db2);
|
||||
var a2 = await ConfigComposer.SnapshotAndFlattenAsync(db2, TestContext.Current.CancellationToken);
|
||||
|
||||
a1.RevisionHash.ShouldBe(a2.RevisionHash);
|
||||
a1.Blob.ShouldBe(a2.Blob);
|
||||
@@ -40,9 +40,9 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
{
|
||||
db.ServerClusters.Add(NewCluster("cluster-a"));
|
||||
db.ServerClusters.Add(NewCluster("cluster-b"));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
var hashAB = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash;
|
||||
var hashAB = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash;
|
||||
|
||||
// Fresh DB, same rows in reverse insertion order.
|
||||
var f2 = NewInMemoryDbFactory();
|
||||
@@ -50,9 +50,9 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
{
|
||||
db.ServerClusters.Add(NewCluster("cluster-b"));
|
||||
db.ServerClusters.Add(NewCluster("cluster-a"));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
var hashBA = (await ConfigComposer.SnapshotAndFlattenAsync(f2.CreateDbContext())).RevisionHash;
|
||||
var hashBA = (await ConfigComposer.SnapshotAndFlattenAsync(f2.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash;
|
||||
|
||||
hashAB.ShouldBe(hashBA);
|
||||
}
|
||||
@@ -65,16 +65,16 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
await using (var db = f.CreateDbContext())
|
||||
{
|
||||
db.ServerClusters.Add(NewCluster("cluster-a"));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
var hashA = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash;
|
||||
var hashA = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash;
|
||||
|
||||
await using (var db = f.CreateDbContext())
|
||||
{
|
||||
db.ServerClusters.Add(NewCluster("cluster-b"));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
var hashAB = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash;
|
||||
var hashAB = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash;
|
||||
|
||||
hashAB.ShouldNotBe(hashA);
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
public async Task Hash_is_64_lowercase_hex_chars()
|
||||
{
|
||||
var f = NewInMemoryDbFactory();
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
artifact.RevisionHash.Length.ShouldBe(64);
|
||||
artifact.RevisionHash.ShouldMatch("^[0-9a-f]{64}$");
|
||||
}
|
||||
@@ -102,10 +102,10 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
await using (var db = f.CreateDbContext())
|
||||
{
|
||||
db.ServerClusters.Add(NewCluster("c1"));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
|
||||
artifact.Blob[0].ShouldBe((byte)0x1F);
|
||||
artifact.Blob[1].ShouldBe((byte)0x8B);
|
||||
@@ -118,7 +118,7 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
using var inflated = new MemoryStream();
|
||||
await using (var gz = new GZipStream(new MemoryStream(artifact.Blob), CompressionMode.Decompress))
|
||||
{
|
||||
await gz.CopyToAsync(inflated);
|
||||
await gz.CopyToAsync(inflated, TestContext.Current.CancellationToken);
|
||||
}
|
||||
var json = inflated.ToArray();
|
||||
Encoding.UTF8.GetString(json).ShouldStartWith("{");
|
||||
@@ -155,11 +155,11 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
DeviceId = "dev-1", DriverInstanceId = "drv-1",
|
||||
Name = "dev-1", DeviceConfig = "{\"HostAddress\":\"10.9.9.9:8193\"}",
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
await using var readDb = f.CreateDbContext();
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(readDb);
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(readDb, TestContext.Current.CancellationToken);
|
||||
var specs = DeploymentArtifact.ParseDriverInstances(artifact.Blob);
|
||||
|
||||
var spec = specs.ShouldHaveSingleItem();
|
||||
@@ -208,11 +208,11 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
DriverInstanceId = "drv-2", ClusterId = "c1",
|
||||
Name = "Modbus2", DriverType = "Modbus", DriverConfig = "{}",
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
await using var readDb = f.CreateDbContext();
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(readDb);
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(readDb, TestContext.Current.CancellationToken);
|
||||
var specs = DeploymentArtifact.ParseDriverInstances(artifact.Blob);
|
||||
|
||||
var withOverride = specs.Single(s => s.DriverInstanceId == "drv-1");
|
||||
@@ -241,11 +241,11 @@ public sealed class ConfigComposerTests : ControlPlaneActorTestBase
|
||||
ApiKeyId = "k1", Name = "scada", SecretHash = "h", Principal = "scadabridge", CreatedUtc = FixedTimestamp,
|
||||
});
|
||||
db.Scripts.Add(new Script { ScriptId = "s-1", Name = "s", SourceCode = "return 1;", SourceHash = "x" });
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var viaDb = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var snapshot = await Configuration.Validation.DraftSnapshotFactory.FromConfigDbAsync(f.CreateDbContext());
|
||||
var viaDb = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
var snapshot = await Configuration.Validation.DraftSnapshotFactory.FromConfigDbAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
var viaSnapshot = ConfigComposer.Compose(snapshot);
|
||||
|
||||
viaSnapshot.RevisionHash.ShouldBe(viaDb.RevisionHash);
|
||||
|
||||
+10
-10
@@ -60,7 +60,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.NodeDeploymentStates.Select(s => s.NodeId).ShouldBe(new[] { "site-a-1:4053" });
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("site-a-1:4053"),
|
||||
ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
@@ -69,7 +69,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,7 +94,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.NodeDeploymentStates.Select(s => s.NodeId).ShouldBe(new[] { "site-a-1:4053" });
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// ...and the enabled node's ack alone seals it. If the disabled row were expected this would
|
||||
// sit at AwaitingApplyAcks until the deadline.
|
||||
@@ -105,7 +105,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,13 +126,13 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// A node nobody registered acks. One expected node, one ack — if it were counted, the
|
||||
// coordinator would seal here.
|
||||
actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("ghost-9:4053"),
|
||||
ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(400));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken);
|
||||
|
||||
using (var db = dbFactory.CreateDbContext())
|
||||
{
|
||||
@@ -147,7 +147,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -170,7 +170,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
db.NodeDeploymentStates.ShouldBeEmpty();
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -202,7 +202,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.NodeDeploymentStates.Select(s => s.NodeId).ShouldBe(new[] { "site-a-1:4053" });
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("site-a-1:4053"),
|
||||
ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
@@ -211,7 +211,7 @@ public sealed class ConfigPublishCoordinatorDbSourcedAcksTests : ControlPlaneAct
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
private static void SeedNodes(
|
||||
|
||||
+22
-22
@@ -46,7 +46,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.NodeDeploymentStates.Select(s => s.NodeId).ShouldBe(new[] { "site-a-1:4053" });
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("site-a-1:4053"),
|
||||
ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
@@ -55,7 +55,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -79,7 +79,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
row.Status.ShouldBe(DeploymentStatus.TimedOut);
|
||||
row.FailureReason.ShouldNotBeNull();
|
||||
row.FailureReason.ShouldContain("lost");
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Positive control: the coordinator is not wedged — a fresh deployment runs end to end.
|
||||
var nextId = SeedDeployment(dbFactory, DeploymentStatus.Dispatching, DateTime.UtcNow);
|
||||
@@ -88,7 +88,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single(d => d.DeploymentId == nextId.Value).Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
actor.Tell(new ApplyAck(nextId, NodeId.Parse("site-a-1:4053"),
|
||||
ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
@@ -96,7 +96,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single(d => d.DeploymentId == nextId.Value).Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -116,7 +116,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
db.NodeDeploymentStates.ShouldBeEmpty();
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -139,10 +139,10 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
// Arm the fault AFTER PreStart has run (it would otherwise fault the recovery read itself and
|
||||
// the actor would be stopped, not restarted). Identify is processed after creation, so a
|
||||
// resolved selection proves PreStart completed.
|
||||
await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget);
|
||||
await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget, TestContext.Current.CancellationToken);
|
||||
faulting.FailNext();
|
||||
admin.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
var first = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
var first = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
first.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
first.DeploymentId.ShouldNotBeNull();
|
||||
|
||||
@@ -153,7 +153,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
using var db = adminFactory.CreateDbContext();
|
||||
db.NodeDeploymentStates.Count(s => s.DeploymentId == first.DeploymentId.Value.Value).ShouldBe(1);
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
coordinator.Tell(new ApplyAck(first.DeploymentId.Value, NodeId.Parse("site-a-1:4053"),
|
||||
ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
@@ -161,11 +161,11 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
{
|
||||
using var db = adminFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Positive control: nothing is wedged — the next deploy is Accepted and seals too.
|
||||
admin.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
var second = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
var second = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
second.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||
coordinator.Tell(new ApplyAck(second.DeploymentId!.Value, NodeId.Parse("site-a-1:4053"),
|
||||
ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
@@ -173,7 +173,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
{
|
||||
using var db = adminFactory.CreateDbContext();
|
||||
db.Deployments.Count(d => d.Status == DeploymentStatus.Sealed).ShouldBe(2);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -189,14 +189,14 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
var dbFactory = NewInMemoryDbFactory();
|
||||
SeedNodes(dbFactory, "site-a-1:4053");
|
||||
var coordinator = Sys.ActorOf(ConfigPublishCoordinator.Props(dbFactory, NoTimeout));
|
||||
await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget); // PreStart saw nothing
|
||||
await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget, TestContext.Current.CancellationToken); // PreStart saw nothing
|
||||
|
||||
var stale = SeedDeployment(dbFactory, DeploymentStatus.Dispatching, DateTime.UtcNow.AddMinutes(-10));
|
||||
var admin = Sys.ActorOf(AdminOperationsActor.Props(
|
||||
dbFactory, coordinator, Enumerable.Empty<IDriverProbe>(), applyDeadline: NoTimeout));
|
||||
|
||||
admin.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
var result = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
var result = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
result.Outcome.ShouldBe(StartDeploymentOutcome.Accepted, result.Message);
|
||||
|
||||
using (var db = dbFactory.CreateDbContext())
|
||||
@@ -214,7 +214,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single(d => d.DeploymentId == result.DeploymentId.Value.Value).Status
|
||||
.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Control for the closure: the same row INSIDE the deadline is still in flight and still refuses.</summary>
|
||||
@@ -228,7 +228,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>(), applyDeadline: NoTimeout));
|
||||
|
||||
admin.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||
var result = ExpectMsg<StartDeploymentResult>(PresenceBudget);
|
||||
var result = ExpectMsg<StartDeploymentResult>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
result.Outcome.ShouldBe(StartDeploymentOutcome.AnotherDeploymentInFlight);
|
||||
result.DeploymentId.ShouldBe(pending);
|
||||
|
||||
@@ -261,7 +261,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single(d => d.DeploymentId == live.Value).Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var verify = dbFactory.CreateDbContext();
|
||||
verify.Deployments.Single(d => d.DeploymentId == done.Value).Status.ShouldBe(terminal);
|
||||
@@ -298,7 +298,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
{
|
||||
using var db = sql.NewContext();
|
||||
db.Deployments.AsNoTracking().Single().Status.ShouldBe(DeploymentStatus.AwaitingApplyAcks);
|
||||
}, duration: PresenceBudget); // PreStart re-drives the un-dispatched row
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken); // PreStart re-drives the un-dispatched row
|
||||
coordinator.Tell(new ApplyAck(id, NodeId.Parse("site-a-1:4053"), ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
|
||||
AwaitAssert(() =>
|
||||
@@ -308,7 +308,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
row.Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
row.SealedAtUtc.ShouldNotBeNull();
|
||||
row.ArtifactBlob.ShouldBe(blob);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -327,7 +327,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
SeedNodes(inner, "site-a-1:4053");
|
||||
var interposer = new InterposingDbFactory(inner);
|
||||
var coordinator = Sys.ActorOf(ConfigPublishCoordinator.Props(interposer, NoTimeout));
|
||||
await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget); // PreStart ran over an empty table
|
||||
await Sys.ActorSelection(coordinator.Path).ResolveOne(PresenceBudget, TestContext.Current.CancellationToken); // PreStart ran over an empty table
|
||||
|
||||
// The race proper: a new Dispatching row whose dispatch is interleaved with an AdminOps-style closure.
|
||||
var victim = SeedDeployment(inner, DeploymentStatus.Dispatching, DateTime.UtcNow);
|
||||
@@ -339,7 +339,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
(int)DeploymentStatus.TimedOut, victim.Value);
|
||||
});
|
||||
coordinator.Tell(new DispatchDeployment(victim, TestRevision, CorrelationId.NewId()));
|
||||
AwaitAssert(() => interposer.Fired.ShouldBeTrue(), duration: PresenceBudget);
|
||||
AwaitAssert(() => interposer.Fired.ShouldBeTrue(), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Positive control + ordering: a live row dispatched after it IS flipped, so the victim's dispatch was processed.
|
||||
var live = SeedDeployment(inner, DeploymentStatus.Dispatching, DateTime.UtcNow);
|
||||
@@ -349,7 +349,7 @@ public sealed class ConfigPublishCoordinatorRecoveryTests : ControlPlaneActorTes
|
||||
using var db = sql.NewContext();
|
||||
db.Deployments.AsNoTracking().Single(d => d.DeploymentId == live.Value).Status
|
||||
.ShouldBe(DeploymentStatus.AwaitingApplyAcks);
|
||||
}, duration: PresenceBudget);
|
||||
}, duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
using var verify = sql.NewContext();
|
||||
verify.Deployments.AsNoTracking().Single(d => d.DeploymentId == victim.Value).Status
|
||||
|
||||
@@ -30,7 +30,7 @@ public sealed class ConfigPublishCoordinatorTests : ControlPlaneActorTestBase
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
var status = db.Deployments.Single().Status;
|
||||
status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that stale apply acknowledgments arriving after seal are ignored.</summary>
|
||||
@@ -48,14 +48,14 @@ public sealed class ConfigPublishCoordinatorTests : ControlPlaneActorTestBase
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Now send a late ApplyAck for the just-sealed deployment. Should be a no-op — neither
|
||||
// crash the actor nor modify the row. We give it a beat and re-check the status.
|
||||
actor.Tell(new ApplyAck(deploymentId, NodeId.Parse("ghost-node"),
|
||||
ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(250));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(250), TestContext.Current.CancellationToken);
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public sealed class ConfigPublishCoordinatorTests : ControlPlaneActorTestBase
|
||||
ApplyAckOutcome.Applied, null, CorrelationId.NewId()));
|
||||
|
||||
// Give it time to process and potentially seal if buggy.
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(400));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(400), TestContext.Current.CancellationToken);
|
||||
|
||||
using var db2 = dbFactory.CreateDbContext();
|
||||
var status = db2.Deployments.Single().Status;
|
||||
|
||||
+5
-5
@@ -53,7 +53,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.TimedOut);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that stale DeadlineElapsed for other deployments is ignored.</summary>
|
||||
@@ -68,7 +68,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest
|
||||
actor.Tell(new ConfigPublishCoordinator.DeadlineElapsed(DeploymentId.NewId()));
|
||||
|
||||
// The seeded one should remain in its starting state (no transition triggered).
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(250));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(250), TestContext.Current.CancellationToken);
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
var status = db.Deployments.Single().Status;
|
||||
status.ShouldBeOneOf(DeploymentStatus.Dispatching, DeploymentStatus.AwaitingApplyAcks, DeploymentStatus.Sealed);
|
||||
@@ -108,7 +108,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest
|
||||
{
|
||||
using var db = dbFactory.CreateDbContext();
|
||||
db.Deployments.Single().Status.ShouldBe(DeploymentStatus.Sealed);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -135,7 +135,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest
|
||||
row.FailureReason.ShouldContain("No ack from: site-b-1:4053.");
|
||||
row.FailureReason.ShouldContain("(1/2 landed)");
|
||||
row.FailureReason.ShouldNotContain("site-a-1");
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>lmxopcua#784 — the timeout reason stays bounded on a large fleet: it names 20 nodes and counts the rest.</summary>
|
||||
@@ -159,7 +159,7 @@ public sealed class ConfigPublishCoordinatorTimeoutTests : ControlPlaneActorTest
|
||||
reason.ShouldNotContain("node-20-");
|
||||
reason.ShouldContain("(and 5 more)");
|
||||
reason.Length.ShouldBeLessThanOrEqualTo(2048);
|
||||
}, duration: TimeSpan.FromSeconds(3));
|
||||
}, duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
private static void SeedNodeRows(
|
||||
|
||||
+18
-18
@@ -33,10 +33,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase
|
||||
{
|
||||
db.ServerClusters.Add(NewCluster("cluster-a"));
|
||||
db.ApiKeys.Add(NewKey("abc123", "scadabridge-dcl"));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA);
|
||||
|
||||
var key = parsed.Keys.ShouldHaveSingleItem();
|
||||
@@ -58,10 +58,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase
|
||||
db.ServerClusters.Add(NewCluster("cluster-a"));
|
||||
db.NodeAcls.Add(NewAcl("acl-1", "cluster-a", "scadabridge-dcl",
|
||||
NodeAclScopeKind.Cluster, NodePermissions.Read | NodePermissions.WriteOperate));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA);
|
||||
|
||||
var grant = parsed.Grants.ShouldHaveSingleItem();
|
||||
@@ -82,10 +82,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase
|
||||
var acl = NewAcl("acl-1", "cluster-a", "p", NodeAclScopeKind.Equipment, NodePermissions.Read);
|
||||
acl.ScopeId = "EQ-1";
|
||||
db.NodeAcls.Add(acl);
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA);
|
||||
|
||||
var grant = parsed.Grants.ShouldHaveSingleItem();
|
||||
@@ -110,10 +110,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase
|
||||
var key = NewKey("abc123", "p");
|
||||
key.ExpiresUtc = expires;
|
||||
db.ApiKeys.Add(key);
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA);
|
||||
|
||||
var parsedKey = parsed.Keys.ShouldHaveSingleItem();
|
||||
@@ -139,10 +139,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase
|
||||
db.ApiKeys.Add(NewKey("key-b", "principal-b"));
|
||||
db.NodeAcls.Add(NewAcl("acl-a", "cluster-a", "principal-a", NodeAclScopeKind.Cluster, NodePermissions.Read));
|
||||
db.NodeAcls.Add(NewAcl("acl-b", "cluster-b", "principal-b", NodeAclScopeKind.Cluster, NodePermissions.Read));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA);
|
||||
|
||||
parsed.ClusterId.ShouldBe("cluster-a");
|
||||
@@ -162,10 +162,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase
|
||||
db.ServerClusters.Add(NewCluster("cluster-b"));
|
||||
db.ClusterNodes.Add(NewNode(NodeB, "cluster-b"));
|
||||
db.NodeAcls.Add(NewAcl("acl-b", "cluster-b", "principal-b", NodeAclScopeKind.Cluster, NodePermissions.Read));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, "node-nowhere:4840");
|
||||
|
||||
parsed.Grants.ShouldBeEmpty();
|
||||
@@ -184,10 +184,10 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase
|
||||
var key = NewKey("abc123", "p");
|
||||
key.Enabled = false;
|
||||
db.ApiKeys.Add(key);
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext());
|
||||
var artifact = await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken);
|
||||
var parsed = DeploymentArtifact.ParseDataPlaneAuthority(artifact.Blob, NodeA);
|
||||
|
||||
parsed.Keys.ShouldHaveSingleItem().Enabled.ShouldBeFalse();
|
||||
@@ -211,16 +211,16 @@ public sealed class DataPlaneAuthorityArtifactTests : ControlPlaneActorTestBase
|
||||
await using (var db = f.CreateDbContext())
|
||||
{
|
||||
db.ServerClusters.Add(NewCluster("cluster-a"));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
var before = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash;
|
||||
var before = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash;
|
||||
|
||||
await using (var db = f.CreateDbContext())
|
||||
{
|
||||
db.ApiKeys.Add(NewKey("abc123", "p"));
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
var after = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext())).RevisionHash;
|
||||
var after = (await ConfigComposer.SnapshotAndFlattenAsync(f.CreateDbContext(), TestContext.Current.CancellationToken)).RevisionHash;
|
||||
|
||||
after.ShouldNotBe(before);
|
||||
}
|
||||
|
||||
@@ -29,20 +29,20 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase
|
||||
|
||||
int pruned;
|
||||
await using (var db = f.CreateDbContext())
|
||||
pruned = await DeploymentRetentionActor.SweepAsync(db, options, SweepNow);
|
||||
pruned = await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken);
|
||||
|
||||
pruned.ShouldBe(7);
|
||||
await using var verify = f.CreateDbContext();
|
||||
foreach (var id in ids.Take(5))
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == id)).ArtifactBlob.Length.ShouldBeGreaterThan(0, "newest 5 keep their blob");
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == id, TestContext.Current.CancellationToken)).ArtifactBlob.Length.ShouldBeGreaterThan(0, "newest 5 keep their blob");
|
||||
foreach (var id in ids.Skip(5))
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == id)).ArtifactBlob.ShouldBeEmpty("older rows are emptied");
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == id, TestContext.Current.CancellationToken)).ArtifactBlob.ShouldBeEmpty("older rows are emptied");
|
||||
// Rows survive: the audit record is intact.
|
||||
(await verify.Deployments.CountAsync()).ShouldBe(12);
|
||||
(await verify.Deployments.CountAsync(TestContext.Current.CancellationToken)).ShouldBe(12);
|
||||
|
||||
// Idempotent: a second sweep finds nothing.
|
||||
await using var again = f.CreateDbContext();
|
||||
(await DeploymentRetentionActor.SweepAsync(again, options, SweepNow)).ShouldBe(0);
|
||||
(await DeploymentRetentionActor.SweepAsync(again, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -76,19 +76,19 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase
|
||||
DeploymentId = inFlight, RevisionHash = new string('f', 64), Status = DeploymentStatus.AwaitingApplyAcks,
|
||||
CreatedBy = "test", CreatedAtUtc = SweepNow.AddDays(-400), ArtifactBlob = new byte[] { 1, 2, 3 },
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
var options = new DeploymentRetentionOptions { KeepSealed = 1, ReferencedDays = 30 };
|
||||
|
||||
await using (var db = f.CreateDbContext())
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(2); // ids[1], ids[2]
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(2); // ids[1], ids[2]
|
||||
|
||||
await using var verify = f.CreateDbContext();
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[0])).ArtifactBlob.ShouldNotBeEmpty("newest");
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[1])).ArtifactBlob.ShouldBeEmpty();
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[2])).ArtifactBlob.ShouldBeEmpty("applied 90 days ago");
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[3])).ArtifactBlob.ShouldNotBeEmpty("applied yesterday");
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == inFlight)).ArtifactBlob.ShouldNotBeEmpty("in flight");
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[0], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldNotBeEmpty("newest");
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[1], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldBeEmpty();
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[2], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldBeEmpty("applied 90 days ago");
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[3], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldNotBeEmpty("applied yesterday");
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == inFlight, TestContext.Current.CancellationToken)).ArtifactBlob.ShouldNotBeEmpty("in flight");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -121,17 +121,17 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase
|
||||
CreatedBy = "test", CreatedAtUtc = SweepNow.AddHours(-i), ArtifactBlob = new byte[] { 9 },
|
||||
});
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
await db.SaveChangesAsync(TestContext.Current.CancellationToken);
|
||||
}
|
||||
var options = new DeploymentRetentionOptions { KeepSealed = 50, ReferencedDays = 30, MaxRowsPerSweep = 200 };
|
||||
|
||||
await using (var db = f.CreateDbContext())
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(60);
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(60);
|
||||
|
||||
await using var verify = f.CreateDbContext();
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == sealedId)).ArtifactBlob
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == sealedId, TestContext.Current.CancellationToken)).ArtifactBlob
|
||||
.ShouldNotBeEmpty("the newest Sealed artifact is never pruned");
|
||||
(await verify.Deployments.CountAsync(d => timedOut.Contains(d.DeploymentId) && d.ArtifactBlob.Length == 0))
|
||||
(await verify.Deployments.CountAsync(d => timedOut.Contains(d.DeploymentId) && d.ArtifactBlob.Length == 0, TestContext.Current.CancellationToken))
|
||||
.ShouldBe(60);
|
||||
}
|
||||
|
||||
@@ -143,11 +143,11 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase
|
||||
var ids = Seed(f, sealedCount: 3, mixInTimedOut: false);
|
||||
|
||||
await using (var db = f.CreateDbContext())
|
||||
(await DeploymentRetentionActor.SweepAsync(db, new DeploymentRetentionOptions { KeepSealed = 0 }, SweepNow))
|
||||
(await DeploymentRetentionActor.SweepAsync(db, new DeploymentRetentionOptions { KeepSealed = 0 }, SweepNow, TestContext.Current.CancellationToken))
|
||||
.ShouldBe(2);
|
||||
|
||||
await using var verify = f.CreateDbContext();
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[0])).ArtifactBlob.ShouldNotBeEmpty();
|
||||
(await verify.Deployments.SingleAsync(d => d.DeploymentId == ids[0], TestContext.Current.CancellationToken)).ArtifactBlob.ShouldNotBeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -158,13 +158,13 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase
|
||||
var options = new DeploymentRetentionOptions { KeepSealed = 2, MaxRowsPerSweep = 3 };
|
||||
|
||||
await using (var db = f.CreateDbContext())
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(3);
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(3);
|
||||
await using (var db = f.CreateDbContext())
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(3);
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(3);
|
||||
await using (var db = f.CreateDbContext())
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(2);
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(2);
|
||||
await using (var db = f.CreateDbContext())
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow)).ShouldBe(0);
|
||||
(await DeploymentRetentionActor.SweepAsync(db, options, SweepNow, TestContext.Current.CancellationToken)).ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>The actor runs the same rule off its thread and reports back.</summary>
|
||||
@@ -180,7 +180,7 @@ public sealed class DeploymentRetentionTests : ControlPlaneActorTestBase
|
||||
|
||||
actor.Tell(DeploymentRetentionActor.Sweep.Instance);
|
||||
|
||||
ExpectMsg<DeploymentRetentionActor.SweepCompleted>(PresenceBudget).Pruned.ShouldBe(2);
|
||||
ExpectMsg<DeploymentRetentionActor.SweepCompleted>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken).Pruned.ShouldBe(2);
|
||||
using var verify = f.CreateDbContext();
|
||||
verify.Deployments.Count(d => d.ArtifactBlob.Length == 0).ShouldBe(2);
|
||||
}
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ public sealed class GossipedRoleCaseInsensitivityTests
|
||||
}),
|
||||
$"redundancy-{Guid.NewGuid():N}");
|
||||
|
||||
var snapshot = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(15));
|
||||
var snapshot = await tcs.Task.WaitAsync(TimeSpan.FromSeconds(15), TestContext.Current.CancellationToken);
|
||||
|
||||
var node = snapshot.Nodes.ShouldHaveSingleItem();
|
||||
node.Role.ShouldBe(
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Akka.Cluster;
|
||||
using Akka.TestKit;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Harness;
|
||||
|
||||
/// <summary>
|
||||
/// Akka TestKit fixture for ControlPlane actor tests. Provides:
|
||||
/// - A test ActorSystem (xunit2 TestKit) wired with PubSub/Cluster extensions.
|
||||
/// - A test ActorSystem (xunit.v3 TestKit) wired with PubSub/Cluster extensions.
|
||||
/// - A fresh in-memory <see cref="OtOpcUaConfigDbContext"/> per harness instance.
|
||||
/// - An <see cref="IDbContextFactory{TContext}"/> the actors can hold.
|
||||
///
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class ManualFailoverServiceTests : IAsyncLifetime
|
||||
}
|
||||
""");
|
||||
|
||||
public async Task InitializeAsync()
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
// Order matters: the seed forms the cluster and is therefore the oldest member.
|
||||
_oldest = ActorSystem.Create("manual-failover", NodeConfig(OldestPort, "manual-failover", OldestPort));
|
||||
@@ -69,7 +69,7 @@ public sealed class ManualFailoverServiceTests : IAsyncLifetime
|
||||
await WaitForUpAsync(_youngest, expectedMembers: 2);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_youngest is not null) await _youngest.Terminate();
|
||||
if (_oldest is not null) await _oldest.Terminate();
|
||||
@@ -128,7 +128,7 @@ public sealed class ManualFailoverServiceTests : IAsyncLifetime
|
||||
|
||||
// The leaving node runs CoordinatedShutdown and terminates — that is what makes the process
|
||||
// exit and be restarted as the youngest member.
|
||||
await _oldest!.WhenTerminated.WaitAsync(TimeSpan.FromSeconds(30));
|
||||
await _oldest!.WhenTerminated.WaitAsync(TimeSpan.FromSeconds(30), TestContext.Current.CancellationToken);
|
||||
|
||||
// ... and the survivor is now what RedundancyStateActor names Primary.
|
||||
var snapshot = await CaptureSnapshotAsync(_youngest!);
|
||||
@@ -192,7 +192,7 @@ public sealed class ManualFailoverServiceTests : IAsyncLifetime
|
||||
snapshotPrimary.ShouldBe(elected.ToString(), "the UI panel must name the same node the button acts on");
|
||||
|
||||
// The dry run really was dry.
|
||||
await Task.Delay(500);
|
||||
await Task.Delay(500, TestContext.Current.CancellationToken);
|
||||
Akka.Cluster.Cluster.Get(_oldest!).State.Members
|
||||
.Count(m => m.Status == MemberStatus.Up)
|
||||
.ShouldBe(2, "a dry run must not remove anyone");
|
||||
|
||||
@@ -74,7 +74,7 @@ public sealed class RedundancyPrimaryElectionTests : IAsyncLifetime
|
||||
}
|
||||
""");
|
||||
|
||||
public async Task InitializeAsync()
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
// Order matters: the seed forms the cluster and is therefore the oldest member.
|
||||
_oldest = ActorSystem.Create("redundancy-election", NodeConfig(OldestPort));
|
||||
@@ -85,7 +85,7 @@ public sealed class RedundancyPrimaryElectionTests : IAsyncLifetime
|
||||
await WaitForUpAsync(_youngest, expectedMembers: 2);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_youngest is not null) await _youngest.Terminate();
|
||||
if (_oldest is not null) await _oldest.Terminate();
|
||||
|
||||
@@ -23,7 +23,7 @@ public sealed class RedundancyStateActorTests : ControlPlaneActorTestBase
|
||||
Sys.ActorOf(RedundancyStateActor.Props(broadcast: msg => probe.Ref.Tell(msg)),
|
||||
"redundancy-actor");
|
||||
|
||||
var msg = probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(3));
|
||||
var msg = probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
msg.Nodes.ShouldNotBeNull();
|
||||
msg.CorrelationId.Value.ShouldNotBe(Guid.Empty);
|
||||
}
|
||||
@@ -37,10 +37,10 @@ public sealed class RedundancyStateActorTests : ControlPlaneActorTestBase
|
||||
"redundancy-debounce");
|
||||
|
||||
// First publish should arrive within the debounce window.
|
||||
probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(3));
|
||||
probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// After debounce settles, no more events are fired by a quiescent cluster.
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -60,10 +60,10 @@ public sealed class RedundancyStateActorTests : ControlPlaneActorTestBase
|
||||
"redundancy-heartbeat");
|
||||
|
||||
// First publish: the change-driven self-join snapshot.
|
||||
probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(2));
|
||||
probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Second publish: the periodic heartbeat re-publish, with NO new cluster event sent.
|
||||
probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(2));
|
||||
probe.ExpectMsg<RedundancyStateChanged>(TimeSpan.FromSeconds(2), cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+2
-3
@@ -7,11 +7,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Akka.TestKit.Xunit2 ties this project to xunit v2 (TestKit isn't ported to v3 yet). -->
|
||||
<PackageReference Include="xunit"/>
|
||||
<PackageReference Include="xunit.v3" VersionOverride="3.2.2"/>
|
||||
<PackageReference Include="Shouldly"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
|
||||
<PackageReference Include="Akka.TestKit.Xunit2"/>
|
||||
<PackageReference Include="Akka.TestKit.Xunit"/>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ public sealed class DriverResilienceStatusPublisherServiceTests
|
||||
public async Task ExecuteAsync_emits_Resilience_item_per_published_snapshot()
|
||||
{
|
||||
// Plain self-joined single-node cluster (mirrors SecretsReplicationRegistrationTests — the
|
||||
// xunit.v3 project cannot use Akka.TestKit.Xunit2). public-hostname is load-bearing for the bind.
|
||||
// xunit.v3 project cannot use Akka.TestKit.Xunit). public-hostname is load-bearing for the bind.
|
||||
var hocon = ConfigurationFactory.ParseString("""
|
||||
akka {
|
||||
loglevel = WARNING
|
||||
|
||||
+1
-1
@@ -128,7 +128,7 @@ public sealed class SecretsReplicationRegistrationTests
|
||||
// the hook, and prove the replication actor exists. On 0.2.0 this fails because the actor
|
||||
// is never spawned (inert replicator); on 0.2.1 it deadlocks in the resolve (scadaproj#1).
|
||||
//
|
||||
// Akka.TestKit.Xunit2 is xunit-v2-only and this project is on xunit.v3 (CS0433), so this
|
||||
// Akka.TestKit.Xunit is xunit-v2-only and this project is on xunit.v3 (CS0433), so this
|
||||
// uses a plain self-joined single-node cluster — which is also all the actor needs: its
|
||||
// constructor gets the DistributedPubSub mediator, no peers required.
|
||||
ActorSystem system = ActorSystem.Create(
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
using Akka.Actor;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Serilog;
|
||||
using Shouldly;
|
||||
@@ -62,7 +62,7 @@ public sealed class VirtualTagToVirtualTagFlowTests : TestKit
|
||||
mux.Tell(Driver(5));
|
||||
publish.FishForMessage<OpcUaPublishActor.AttributeValueUpdate>(
|
||||
m => m.NodeId == AKey && Equals(m.Value, 10d), TimeSpan.FromMilliseconds(500));
|
||||
}, Budget, TimeSpan.FromMilliseconds(50));
|
||||
}, Budget, TimeSpan.FromMilliseconds(50), TestContext.Current.CancellationToken);
|
||||
NextFor(publish, BKey, m => Equals(m.Value, 11d) && m.StatusCode == OpcUaStatus.Good);
|
||||
|
||||
// A's input changes: B re-evaluates against A's NEW value (ctx.GetTag returns A's current value).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Akka.Hosting;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Grpc.Core;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Shouldly;
|
||||
@@ -54,16 +54,16 @@ public sealed class NodeCommandGrpcServiceTests : TestKit
|
||||
var alarm = new AlarmCommand("alarm-7", "Acknowledge", "carol", "looked at it", null);
|
||||
|
||||
(await service.Execute(MeshTransportDtoMapper.ToEnvelope(dispatch), Ctx())).ShouldNotBeNull();
|
||||
probe.ExpectMsg<DispatchDeployment>().ShouldBe(dispatch);
|
||||
probe.ExpectMsg<DispatchDeployment>(cancellationToken: TestContext.Current.CancellationToken).ShouldBe(dispatch);
|
||||
|
||||
await service.Execute(MeshTransportDtoMapper.ToEnvelope(restart), Ctx());
|
||||
probe.ExpectMsg<RestartDriver>().ShouldBe(restart);
|
||||
probe.ExpectMsg<RestartDriver>(cancellationToken: TestContext.Current.CancellationToken).ShouldBe(restart);
|
||||
|
||||
await service.Execute(MeshTransportDtoMapper.ToEnvelope(reconnect), Ctx());
|
||||
probe.ExpectMsg<ReconnectDriver>().ShouldBe(reconnect);
|
||||
probe.ExpectMsg<ReconnectDriver>(cancellationToken: TestContext.Current.CancellationToken).ShouldBe(reconnect);
|
||||
|
||||
await service.Execute(MeshTransportDtoMapper.ToEnvelope(alarm, AlarmCorrelation), Ctx());
|
||||
probe.ExpectMsg<AlarmCommand>().ShouldBe(alarm);
|
||||
probe.ExpectMsg<AlarmCommand>(cancellationToken: TestContext.Current.CancellationToken).ShouldBe(alarm);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -94,7 +94,7 @@ public sealed class NodeCommandGrpcServiceTests : TestKit
|
||||
new CommandEnvelope { CorrelationId = Guid.NewGuid().ToString("N") }, Ctx()));
|
||||
|
||||
ex.StatusCode.ShouldBe(StatusCode.InvalidArgument);
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -109,7 +109,7 @@ public sealed class NodeCommandGrpcServiceTests : TestKit
|
||||
var ex = await Should.ThrowAsync<RpcException>(() => service.Execute(envelope, Ctx()));
|
||||
|
||||
ex.StatusCode.ShouldBe(StatusCode.InvalidArgument);
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
probe.ExpectNoMsg(TimeSpan.FromMilliseconds(200), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
public static TheoryData<CommandEnvelope> UndecodableEnvelopes() =>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Akka.Actor;
|
||||
using Akka.Configuration;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
@@ -106,7 +106,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
{
|
||||
var (presented, store) = SetUp(NodePermissions.WriteOperate, NodeAclScopeKind.Cluster);
|
||||
|
||||
var result = await Auth(store).AuthenticateUserNameAsync("apikey", presented, default);
|
||||
var result = await Auth(store).AuthenticateUserNameAsync("apikey", presented, TestContext.Current.CancellationToken);
|
||||
|
||||
result.Success.ShouldBeTrue();
|
||||
result.DisplayName.ShouldBe(Principal);
|
||||
@@ -117,7 +117,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
public async Task The_wrong_username_is_denied()
|
||||
{
|
||||
var (presented, store) = SetUp();
|
||||
var result = await Auth(store).AuthenticateUserNameAsync("alice", presented, default);
|
||||
var result = await Auth(store).AuthenticateUserNameAsync("alice", presented, TestContext.Current.CancellationToken);
|
||||
result.Success.ShouldBeFalse();
|
||||
}
|
||||
|
||||
@@ -127,10 +127,10 @@ public sealed class ApiKeyAuthenticationTests
|
||||
var (presented, store) = SetUp();
|
||||
var auth = Auth(store);
|
||||
|
||||
var wrongSecret = await auth.AuthenticateUserNameAsync("apikey", $"otua_{store.KeyId}_wrong", default);
|
||||
var noSuchKey = await auth.AuthenticateUserNameAsync("apikey", "otua_ffffffff_whatever", default);
|
||||
var malformed = await auth.AuthenticateUserNameAsync("apikey", "not-a-key", default);
|
||||
var empty = await auth.AuthenticateUserNameAsync("apikey", "", default);
|
||||
var wrongSecret = await auth.AuthenticateUserNameAsync("apikey", $"otua_{store.KeyId}_wrong", TestContext.Current.CancellationToken);
|
||||
var noSuchKey = await auth.AuthenticateUserNameAsync("apikey", "otua_ffffffff_whatever", TestContext.Current.CancellationToken);
|
||||
var malformed = await auth.AuthenticateUserNameAsync("apikey", "not-a-key", TestContext.Current.CancellationToken);
|
||||
var empty = await auth.AuthenticateUserNameAsync("apikey", "", TestContext.Current.CancellationToken);
|
||||
|
||||
foreach (var r in new[] { wrongSecret, noSuchKey, malformed, empty })
|
||||
{
|
||||
@@ -141,7 +141,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
}
|
||||
|
||||
// ...and the valid one still works, so the above is not passing because everything is denied.
|
||||
(await auth.AuthenticateUserNameAsync("apikey", presented, default)).Success.ShouldBeTrue();
|
||||
(await auth.AuthenticateUserNameAsync("apikey", presented, TestContext.Current.CancellationToken)).Success.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -150,7 +150,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
// The interface contract forbids throwing — an exception escapes into the SDK session-activation
|
||||
// path and surfaces as BadInternalError instead of a clean rejection.
|
||||
var store = new ThrowingApiKeyStore();
|
||||
var result = await Auth(store).AuthenticateUserNameAsync("apikey", "otua_aabbccdd_secret", default);
|
||||
var result = await Auth(store).AuthenticateUserNameAsync("apikey", "otua_aabbccdd_secret", TestContext.Current.CancellationToken);
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
result.Error.ShouldBe(ApiKeyOpcUaUserAuthenticator.DenyMessage);
|
||||
@@ -165,7 +165,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
Options.Create(new ApiKeyOptions { Username = "apikey", Pepper = "" }),
|
||||
NullLogger<ApiKeyOpcUaUserAuthenticator>.Instance);
|
||||
|
||||
(await auth.AuthenticateUserNameAsync("apikey", presented, default)).Success.ShouldBeFalse();
|
||||
(await auth.AuthenticateUserNameAsync("apikey", presented, TestContext.Current.CancellationToken)).Success.ShouldBeFalse();
|
||||
}
|
||||
|
||||
// ---------- anonymous as a normal principal (D3) ----------
|
||||
@@ -183,7 +183,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
PermissionFlags = NodePermissions.WriteOperate,
|
||||
});
|
||||
|
||||
var result = await Auth(store).AuthenticateAnonymousAsync(default);
|
||||
var result = await Auth(store).AuthenticateAnonymousAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
result.Success.ShouldBeTrue();
|
||||
result.DisplayName.ShouldBe(OpcUaPrincipals.Anonymous);
|
||||
@@ -197,7 +197,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
// Today's behaviour preserved: it connects, and every role-gated operation denies.
|
||||
var (_, store) = SetUp();
|
||||
|
||||
var result = await Auth(store).AuthenticateAnonymousAsync(default);
|
||||
var result = await Auth(store).AuthenticateAnonymousAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
result.Success.ShouldBeTrue();
|
||||
result.Roles.ShouldBeEmpty();
|
||||
@@ -208,7 +208,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
{
|
||||
// Deliberately NOT admit-with-no-roles. That is the pre-#520 behaviour, and defaulting to it
|
||||
// exactly when we cannot read what anonymous may do stops being harmless once reads are gated.
|
||||
var result = await Auth(new ThrowingApiKeyStore()).AuthenticateAnonymousAsync(default);
|
||||
var result = await Auth(new ThrowingApiKeyStore()).AuthenticateAnonymousAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
}
|
||||
@@ -227,7 +227,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
CreatedUtc = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
var result = await Auth(new FakeApiKeyStore(key, [])).AuthenticateUserNameAsync("apikey", presented, default);
|
||||
var result = await Auth(new FakeApiKeyStore(key, [])).AuthenticateUserNameAsync("apikey", presented, TestContext.Current.CancellationToken);
|
||||
|
||||
// Otherwise a credentialled client silently inherits whatever anonymous was granted.
|
||||
result.Success.ShouldBeFalse();
|
||||
@@ -241,7 +241,7 @@ public sealed class ApiKeyAuthenticationTests
|
||||
// Interface-typed on purpose: a default interface implementation is only reachable through the
|
||||
// interface, which is how OpcUaApplicationHost holds it.
|
||||
IOpcUaUserAuthenticator sut = NullOpcUaUserAuthenticator.Instance;
|
||||
var result = await sut.AuthenticateAnonymousAsync(default);
|
||||
var result = await sut.AuthenticateAnonymousAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
result.Success.ShouldBeTrue();
|
||||
result.Roles.ShouldBeEmpty();
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed class ArtifactApiKeyStoreTests
|
||||
{
|
||||
var sut = new ArtifactApiKeyStore(new DataPlaneAuthorityState());
|
||||
|
||||
(await sut.FindAsync("abc", Now, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("abc", Now, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -37,7 +37,7 @@ public sealed class ArtifactApiKeyStoreTests
|
||||
[Key("abc", "scadabridge-dcl")],
|
||||
[Acl("acl-1", "scadabridge-dcl", NodeAclScopeKind.Cluster, NodePermissions.WriteOperate)], NoScopePaths);
|
||||
|
||||
var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, default);
|
||||
var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, TestContext.Current.CancellationToken);
|
||||
|
||||
found.ShouldNotBeNull();
|
||||
found!.Key.Principal.ShouldBe("scadabridge-dcl");
|
||||
@@ -67,7 +67,7 @@ public sealed class ArtifactApiKeyStoreTests
|
||||
Acl("acl-equipment", "p", NodeAclScopeKind.Equipment, NodePermissions.WriteOperate),
|
||||
], NoScopePaths);
|
||||
|
||||
var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, default);
|
||||
var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, TestContext.Current.CancellationToken);
|
||||
|
||||
found!.Grants.Select(g => g.NodeAclId).ShouldBe(["acl-cluster", "acl-equipment"], ignoreOrder: true);
|
||||
}
|
||||
@@ -91,7 +91,7 @@ public sealed class ArtifactApiKeyStoreTests
|
||||
[Acl("acl-equipment", "p", NodeAclScopeKind.Equipment, NodePermissions.WriteOperate)],
|
||||
NoScopePaths);
|
||||
|
||||
var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, default);
|
||||
var found = await new ArtifactApiKeyStore(state).FindAsync("abc", Now, TestContext.Current.CancellationToken);
|
||||
|
||||
found!.Grants.ShouldHaveSingleItem();
|
||||
ApiKeyInterimRoleMapper.Map(found.Grants, "p").ShouldBeEmpty();
|
||||
@@ -106,13 +106,13 @@ public sealed class ArtifactApiKeyStoreTests
|
||||
var sut = new ArtifactApiKeyStore(state);
|
||||
|
||||
state.Apply("cluster-a", "rev-1", [Key("abc", "p")], [], NoScopePaths);
|
||||
(await sut.FindAsync("abc", Now, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync("abc", Now, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
|
||||
var revoked = Key("abc", "p");
|
||||
revoked.Enabled = false;
|
||||
state.Apply("cluster-a", "rev-2", [revoked], [], NoScopePaths);
|
||||
|
||||
(await sut.FindAsync("abc", Now, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("abc", Now, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -124,7 +124,7 @@ public sealed class ArtifactApiKeyStoreTests
|
||||
[Acl("acl-anon", OpcUaPrincipals.Anonymous, NodeAclScopeKind.Cluster, NodePermissions.Read)],
|
||||
NoScopePaths);
|
||||
|
||||
var grants = await new ArtifactApiKeyStore(state).FindGrantsAsync(OpcUaPrincipals.Anonymous, default);
|
||||
var grants = await new ArtifactApiKeyStore(state).FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken);
|
||||
|
||||
grants.ShouldHaveSingleItem().PermissionFlags.ShouldBe(NodePermissions.Read);
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ public sealed class BoundedApiKeyStoreKnownIdTests
|
||||
var clock = new FakeClock(T0);
|
||||
var sut = Bounded(inner, clock, cacheSeconds: 5);
|
||||
|
||||
(await sut.FindAsync("k-guess-1", T0.UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess-2", T0.UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess-1", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess-2", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(0, "unknown ids never reach the store");
|
||||
inner.ListCalls.ShouldBe(1, "one set load serves every unknown id inside the TTL");
|
||||
|
||||
// Positive control: the real id reaches the store and is found.
|
||||
(await sut.FindAsync("k-real", T0.UtcDateTime, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync("k-real", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
inner.FindCalls.ShouldBe(1);
|
||||
}
|
||||
|
||||
@@ -42,15 +42,15 @@ public sealed class BoundedApiKeyStoreKnownIdTests
|
||||
var clock = new FakeClock(T0);
|
||||
var sut = Bounded(inner, clock, cacheSeconds: 5);
|
||||
|
||||
(await sut.FindAsync("k-new", T0.UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-new", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.Mint("k-new");
|
||||
// Still inside the TTL: the stale set refuses it (the documented window).
|
||||
clock.Advance(TimeSpan.FromSeconds(3));
|
||||
(await sut.FindAsync("k-new", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-new", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(0);
|
||||
|
||||
clock.Advance(TimeSpan.FromSeconds(3));
|
||||
(await sut.FindAsync("k-new", clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync("k-new", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
inner.ListCalls.ShouldBe(2);
|
||||
}
|
||||
|
||||
@@ -62,20 +62,20 @@ public sealed class BoundedApiKeyStoreKnownIdTests
|
||||
var sut = Bounded(inner, clock, cacheSeconds: 5);
|
||||
|
||||
// No set yet and the load fails: the per-id path runs (old behaviour), nothing is wedged.
|
||||
(await sut.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(1);
|
||||
sut.KnownKeyIds.ShouldBeNull();
|
||||
|
||||
inner.ListThrows = false;
|
||||
clock.Advance(TimeSpan.FromSeconds(6));
|
||||
(await sut.FindAsync("k-guess-2", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess-2", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
sut.KnownKeyIds.ShouldNotBeNull();
|
||||
inner.FindCalls.ShouldBe(1, "once the set loaded, the guess was refused in memory");
|
||||
|
||||
// A later failed refresh keeps the previous set in force.
|
||||
inner.ListThrows = true;
|
||||
clock.Advance(TimeSpan.FromSeconds(6));
|
||||
(await sut.FindAsync("k-guess-3", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess-3", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(1);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public sealed class BoundedApiKeyStoreKnownIdTests
|
||||
var sut = Bounded(inner, clock, cacheSeconds: 0, missBurst: 3, refillPerSecond: 1);
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
(await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(3);
|
||||
|
||||
// Budget exhausted: refused without a store read, as a throw the authenticator denies on.
|
||||
@@ -97,14 +97,14 @@ public sealed class BoundedApiKeyStoreKnownIdTests
|
||||
|
||||
// Refill: one second restores one token.
|
||||
clock.Advance(TimeSpan.FromSeconds(1));
|
||||
(await sut.FindAsync("k-guess-10", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess-10", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(4);
|
||||
|
||||
// A hit is not a miss: it reaches the store even with the bucket empty? No — it needs a token to
|
||||
// be attempted, but the token is refunded, so the next attempt still has it. Prove the refund.
|
||||
clock.Advance(TimeSpan.FromSeconds(1));
|
||||
(await sut.FindAsync("k-real", clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync("k-guess-11", clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-real", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
(await sut.FindAsync("k-guess-11", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(6);
|
||||
}
|
||||
|
||||
@@ -114,8 +114,8 @@ public sealed class BoundedApiKeyStoreKnownIdTests
|
||||
var inner = new ListingStore("k-real");
|
||||
var sut = Bounded(inner, new FakeClock(T0), cacheSeconds: 0, missBurst: 0);
|
||||
|
||||
(await sut.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(2);
|
||||
inner.ListCalls.ShouldBe(0);
|
||||
}
|
||||
@@ -139,13 +139,13 @@ public sealed class BoundedApiKeyStoreKnownIdTests
|
||||
var unlisting = new ListingStore("k-real") { SupportsListing = false };
|
||||
var first = new BoundedApiKeyStore(unlisting, options, NullLogger<BoundedApiKeyStore>.Instance, clock, guard);
|
||||
|
||||
(await first.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull();
|
||||
(await first.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
unlisting.FindCalls.ShouldBe(1, "no set: the per-id path ran");
|
||||
guard.KnownIdSet.ShouldBeTrue("the shared options object is never written");
|
||||
|
||||
var listing = new ListingStore("k-real");
|
||||
var second = new BoundedApiKeyStore(listing, options, NullLogger<BoundedApiKeyStore>.Instance, clock, guard);
|
||||
(await second.FindAsync("k-guess", T0.UtcDateTime, default)).ShouldBeNull();
|
||||
(await second.FindAsync("k-guess", T0.UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
listing.FindCalls.ShouldBe(0, "the second decorator still refuses unknown ids in memory");
|
||||
listing.ListCalls.ShouldBe(1);
|
||||
}
|
||||
|
||||
+10
-10
@@ -31,7 +31,7 @@ public sealed class BoundedApiKeyStorePerEndpointTests
|
||||
var sut = Bounded(inner, new FakeClock(T0), missBurst: 100, perEndpointBurst: 3);
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
(await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, Noisy, default)).ShouldBeNull();
|
||||
(await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(3);
|
||||
|
||||
var refused = await Should.ThrowAsync<InvalidOperationException>(
|
||||
@@ -40,7 +40,7 @@ public sealed class BoundedApiKeyStorePerEndpointTests
|
||||
inner.FindCalls.ShouldBe(3, "the exhausted client is refused without a store read");
|
||||
|
||||
// Positive control: another client still reaches the store.
|
||||
(await sut.FindAsync("k-guess-q", T0.UtcDateTime, Quiet, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-guess-q", T0.UtcDateTime, Quiet, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(4);
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ public sealed class BoundedApiKeyStorePerEndpointTests
|
||||
var inner = new UnlistingStore("k-real");
|
||||
var sut = Bounded(inner, new FakeClock(T0), missBurst: 2, perEndpointBurst: 10);
|
||||
|
||||
(await sut.FindAsync("k-a", T0.UtcDateTime, Noisy, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-b", T0.UtcDateTime, remoteAddress: null, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-a", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-b", T0.UtcDateTime, remoteAddress: null, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
|
||||
var refused = await Should.ThrowAsync<InvalidOperationException>(
|
||||
() => sut.FindAsync("k-c", T0.UtcDateTime, Quiet, default));
|
||||
@@ -70,14 +70,14 @@ public sealed class BoundedApiKeyStorePerEndpointTests
|
||||
var sut = Bounded(inner, clock, missBurst: 100, perEndpointBurst: 2, perEndpointRefill: 1);
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
(await sut.FindAsync("k-real", T0.UtcDateTime, Noisy, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync("k-real", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
|
||||
(await sut.FindAsync("k-g1", T0.UtcDateTime, Noisy, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-g2", T0.UtcDateTime, Noisy, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-g1", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-g2", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
await Should.ThrowAsync<InvalidOperationException>(() => sut.FindAsync("k-g3", T0.UtcDateTime, Noisy, default));
|
||||
|
||||
clock.Advance(TimeSpan.FromSeconds(1));
|
||||
(await sut.FindAsync("k-g4", clock.GetUtcNow().UtcDateTime, Noisy, default)).ShouldBeNull();
|
||||
(await sut.FindAsync("k-g4", clock.GetUtcNow().UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(8);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public sealed class BoundedApiKeyStorePerEndpointTests
|
||||
var sut = Bounded(inner, new FakeClock(T0), missBurst: 100, perEndpointBurst: 0);
|
||||
|
||||
for (var i = 0; i < 20; i++)
|
||||
(await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, Noisy, default)).ShouldBeNull();
|
||||
(await sut.FindAsync($"k-guess-{i}", T0.UtcDateTime, Noisy, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.FindCalls.ShouldBe(20);
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ public sealed class BoundedApiKeyStorePerEndpointTests
|
||||
NullLogger<ApiKeyOpcUaUserAuthenticator>.Instance);
|
||||
|
||||
var result = await authenticator.AuthenticateUserNameAsync(
|
||||
"apikey", "otua_k-guess_secret", new IPEndPoint(Noisy, 50123), default);
|
||||
"apikey", "otua_k-guess_secret", new IPEndPoint(Noisy, 50123), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
store.LastAddress.ShouldBe(Noisy);
|
||||
|
||||
@@ -54,7 +54,7 @@ public sealed class BoundedApiKeyStoreTests
|
||||
var auth = Auth(Bounded(inner, timeoutMs: 150, cacheSeconds: 0));
|
||||
|
||||
var result = await auth.AuthenticateUserNameAsync("apikey", presented, CancellationToken.None)
|
||||
.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
result.Error.ShouldBe(ApiKeyOpcUaUserAuthenticator.DenyMessage);
|
||||
@@ -95,10 +95,10 @@ public sealed class BoundedApiKeyStoreTests
|
||||
var pending = auth.AuthenticateUserNameAsync("apikey", presented, CancellationToken.None);
|
||||
|
||||
// Let the deadline pass, THEN hand the inner store its (valid) answer.
|
||||
await Task.Delay(400);
|
||||
await Task.Delay(400, TestContext.Current.CancellationToken);
|
||||
gate.SetResult();
|
||||
|
||||
var result = await pending.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
var result = await pending.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
result.Success.ShouldBeFalse();
|
||||
result.Error.ShouldBe(ApiKeyOpcUaUserAuthenticator.DenyMessage);
|
||||
}
|
||||
@@ -167,10 +167,10 @@ public sealed class BoundedApiKeyStoreTests
|
||||
|
||||
// The abandoned inner task now completes, with a perfectly valid key, into nobody's hands.
|
||||
inner.Release();
|
||||
await inner.Completed.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
await inner.Completed.WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken);
|
||||
|
||||
// Well inside the 60 s TTL: a cached late answer would make this a hit.
|
||||
(await sut.FindAsync(inner.KeyId, DateTime.UtcNow, default).WaitAsync(TimeSpan.FromSeconds(2)))
|
||||
(await sut.FindAsync(inner.KeyId, DateTime.UtcNow, TestContext.Current.CancellationToken).WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken))
|
||||
.ShouldNotBeNull("the store answers immediately once released");
|
||||
inner.Calls.ShouldBe(2, "the abandoned lookup must not have populated the cache");
|
||||
}
|
||||
@@ -197,12 +197,12 @@ public sealed class BoundedApiKeyStoreTests
|
||||
.WaitAsync(TimeSpan.FromSeconds(2)));
|
||||
|
||||
inner.ReleaseWithFault(new InvalidOperationException("SqlClient gave up"));
|
||||
await inner.Completed.WaitAsync(TimeSpan.FromSeconds(2));
|
||||
await inner.Completed.WaitAsync(TimeSpan.FromSeconds(2), TestContext.Current.CancellationToken);
|
||||
|
||||
// The continuation is a separate task from the one just awaited; give it a moment to run.
|
||||
var deadline = Stopwatch.StartNew();
|
||||
while (log.Lines.Count == 0 && deadline.Elapsed < TimeSpan.FromSeconds(2))
|
||||
await Task.Delay(10);
|
||||
await Task.Delay(10, TestContext.Current.CancellationToken);
|
||||
|
||||
log.Lines.ShouldContain(line => line.Contains("abandoned", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
@@ -222,11 +222,11 @@ public sealed class BoundedApiKeyStoreTests
|
||||
var pending = auth.AuthenticateUserNameAsync("apikey", presented, CancellationToken.None);
|
||||
|
||||
// Far longer than any deadline this class configures; an active bound would have denied by now.
|
||||
await Task.Delay(300);
|
||||
await Task.Delay(300, TestContext.Current.CancellationToken);
|
||||
pending.IsCompleted.ShouldBeFalse("a disabled deadline must not cancel a slow lookup");
|
||||
|
||||
gate.SetResult();
|
||||
(await pending.WaitAsync(TimeSpan.FromSeconds(5))).Success.ShouldBeTrue();
|
||||
(await pending.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken)).Success.ShouldBeTrue();
|
||||
}
|
||||
|
||||
/// <summary>The anonymous activation path gets the same deadline, and the same denial.</summary>
|
||||
@@ -242,7 +242,7 @@ public sealed class BoundedApiKeyStoreTests
|
||||
var auth = Auth(Bounded(inner, timeoutMs: 150, cacheSeconds: 0));
|
||||
|
||||
var result = await auth.AuthenticateAnonymousAsync(CancellationToken.None)
|
||||
.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
.WaitAsync(TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
|
||||
|
||||
result.Success.ShouldBeFalse();
|
||||
}
|
||||
@@ -257,9 +257,9 @@ public sealed class BoundedApiKeyStoreTests
|
||||
var clock = new FakeClock(new DateTimeOffset(2026, 8, 20, 12, 0, 0, TimeSpan.Zero));
|
||||
var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 5, clock);
|
||||
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
clock.Advance(TimeSpan.FromSeconds(2));
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
|
||||
inner.Calls.ShouldBe(1);
|
||||
}
|
||||
@@ -280,18 +280,18 @@ public sealed class BoundedApiKeyStoreTests
|
||||
var clock = new FakeClock(new DateTimeOffset(2026, 8, 20, 12, 0, 0, TimeSpan.Zero));
|
||||
var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 5, clock);
|
||||
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default))
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken))
|
||||
.ShouldNotBeNull("non-vacuity: the key really was usable before the revocation");
|
||||
|
||||
inner.Revoked = true;
|
||||
|
||||
// Still inside the TTL — the documented, accepted staleness window.
|
||||
clock.Advance(TimeSpan.FromSeconds(3));
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
|
||||
// Past it — the store is re-read and the revocation lands.
|
||||
clock.Advance(TimeSpan.FromSeconds(3));
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.Calls.ShouldBe(2);
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ public sealed class BoundedApiKeyStoreTests
|
||||
var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 0, clock);
|
||||
|
||||
for (var i = 0; i < 3; i++)
|
||||
await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default);
|
||||
await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken);
|
||||
|
||||
inner.Calls.ShouldBe(3);
|
||||
}
|
||||
@@ -328,7 +328,7 @@ public sealed class BoundedApiKeyStoreTests
|
||||
() => sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default));
|
||||
|
||||
// Same instant — a cached fault would still be "fresh" here, so the clock cannot rescue this.
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
inner.Calls.ShouldBe(2);
|
||||
}
|
||||
|
||||
@@ -340,12 +340,12 @@ public sealed class BoundedApiKeyStoreTests
|
||||
var clock = new FakeClock(new DateTimeOffset(2026, 8, 20, 12, 0, 0, TimeSpan.Zero));
|
||||
var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 5, clock);
|
||||
|
||||
await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, default);
|
||||
await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, default);
|
||||
await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken);
|
||||
await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken);
|
||||
inner.Calls.ShouldBe(1);
|
||||
|
||||
clock.Advance(TimeSpan.FromSeconds(6));
|
||||
await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, default);
|
||||
await sut.FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken);
|
||||
inner.Calls.ShouldBe(2);
|
||||
}
|
||||
|
||||
@@ -369,15 +369,15 @@ public sealed class BoundedApiKeyStoreTests
|
||||
guard: new ApiKeyStoreGuardOptions { MissBurst = 0 });
|
||||
|
||||
// Seed the real key, then flood past the ceiling with ids that do not exist.
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
for (var i = 0; i < BoundedApiKeyStore.MaxCacheEntries + 1; i++)
|
||||
await sut.FindAsync($"ghost-{i}", clock.GetUtcNow().UtcDateTime, default);
|
||||
await sut.FindAsync($"ghost-{i}", clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken);
|
||||
|
||||
var callsAfterFlood = inner.Calls;
|
||||
|
||||
// Still correct after the clear — and the real key was evicted, so it is re-read rather than
|
||||
// served from a cache the flood was supposed to have emptied.
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldNotBeNull();
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldNotBeNull();
|
||||
inner.Calls.ShouldBe(callsAfterFlood + 1);
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ public sealed class BoundedApiKeyStoreTests
|
||||
var clock = new FakeClock(start);
|
||||
var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 60, clock);
|
||||
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default))
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken))
|
||||
.ShouldNotBeNull("non-vacuity: the key was usable and IS now cached");
|
||||
|
||||
inner.Revoked = true;
|
||||
@@ -414,7 +414,7 @@ public sealed class BoundedApiKeyStoreTests
|
||||
// Past ExpiresUtc (+2 s) but far inside the 60 s TTL — the entry is still "fresh" to the cache.
|
||||
clock.Advance(TimeSpan.FromSeconds(5));
|
||||
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default)).ShouldBeNull();
|
||||
(await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
inner.Calls.ShouldBe(2, "the expired entry must be re-read, not served");
|
||||
}
|
||||
|
||||
@@ -449,10 +449,10 @@ public sealed class BoundedApiKeyStoreTests
|
||||
using var meter = new MeterRecorder();
|
||||
var sut = Bounded(inner, timeoutMs: 0, cacheSeconds: 5, clock);
|
||||
|
||||
await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default);
|
||||
await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken);
|
||||
meter.WithOutcome("cache_hit").ShouldBe(0, "the first call is a miss");
|
||||
|
||||
await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, default);
|
||||
await sut.FindAsync(inner.KeyId, clock.GetUtcNow().UtcDateTime, TestContext.Current.CancellationToken);
|
||||
meter.WithOutcome("cache_hit").ShouldBe(1);
|
||||
}
|
||||
|
||||
@@ -468,7 +468,7 @@ public sealed class BoundedApiKeyStoreTests
|
||||
using var meter = new MeterRecorder();
|
||||
var sut = Bounded(inner, timeoutMs: 2000, cacheSeconds: 0);
|
||||
|
||||
await sut.FindAsync(inner.KeyId, DateTime.UtcNow, default);
|
||||
await sut.FindAsync(inner.KeyId, DateTime.UtcNow, TestContext.Current.CancellationToken);
|
||||
|
||||
meter.Total.ShouldBe(0);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class ConfigDbApiKeyStoreTests
|
||||
keys: [Key("abc", "scadabridge-dcl")],
|
||||
acls: [Acl("acl-other", OtherCluster, "scadabridge-dcl", NodeAclScopeKind.Cluster, NodePermissions.WriteOperate)]);
|
||||
|
||||
var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, default);
|
||||
var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, TestContext.Current.CancellationToken);
|
||||
|
||||
// The KEY still resolves — keys are fleet-wide credentials (ApiKey carries no ClusterId); it is
|
||||
// the grants that are cluster-bound. Asserting a null lookup here would pass for the wrong reason.
|
||||
@@ -76,7 +76,7 @@ public sealed class ConfigDbApiKeyStoreTests
|
||||
Acl("acl-other", OtherCluster, "scadabridge-dcl", NodeAclScopeKind.Cluster, NodePermissions.WriteOperate),
|
||||
]);
|
||||
|
||||
var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, default);
|
||||
var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, TestContext.Current.CancellationToken);
|
||||
|
||||
found!.Grants.ShouldHaveSingleItem().NodeAclId.ShouldBe("acl-mine");
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public sealed class ConfigDbApiKeyStoreTests
|
||||
Acl("acl-b", OtherCluster, "p", NodeAclScopeKind.Cluster, NodePermissions.WriteOperate),
|
||||
]);
|
||||
|
||||
var found = await new ConfigDbApiKeyStore(factory, clusterId: null).FindAsync("abc", Now, default);
|
||||
var found = await new ConfigDbApiKeyStore(factory, clusterId: null).FindAsync("abc", Now, TestContext.Current.CancellationToken);
|
||||
|
||||
found!.Grants.Select(g => g.NodeAclId).ShouldBe(["acl-a", "acl-b"], ignoreOrder: true);
|
||||
}
|
||||
@@ -119,7 +119,7 @@ public sealed class ConfigDbApiKeyStoreTests
|
||||
keys: [Key("abc", "p")],
|
||||
acls: [Acl("acl-mine", "SITE-A", "p", NodeAclScopeKind.Cluster, NodePermissions.Read)]);
|
||||
|
||||
var found = await new ConfigDbApiKeyStore(factory, "site-a").FindAsync("abc", Now, default);
|
||||
var found = await new ConfigDbApiKeyStore(factory, "site-a").FindAsync("abc", Now, TestContext.Current.CancellationToken);
|
||||
|
||||
found!.Grants.ShouldHaveSingleItem().NodeAclId.ShouldBe("acl-mine");
|
||||
}
|
||||
@@ -141,7 +141,7 @@ public sealed class ConfigDbApiKeyStoreTests
|
||||
]);
|
||||
|
||||
var grants = await new ConfigDbApiKeyStore(factory, ThisCluster)
|
||||
.FindGrantsAsync(OpcUaPrincipals.Anonymous, default);
|
||||
.FindGrantsAsync(OpcUaPrincipals.Anonymous, TestContext.Current.CancellationToken);
|
||||
|
||||
grants.ShouldHaveSingleItem().NodeAclId.ShouldBe("acl-anon-mine");
|
||||
}
|
||||
@@ -168,7 +168,7 @@ public sealed class ConfigDbApiKeyStoreTests
|
||||
Acl("acl-equipment", ThisCluster, "p", NodeAclScopeKind.Equipment, NodePermissions.WriteOperate),
|
||||
]);
|
||||
|
||||
var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, default);
|
||||
var found = await new ConfigDbApiKeyStore(factory, ThisCluster).FindAsync("abc", Now, TestContext.Current.CancellationToken);
|
||||
|
||||
found!.Grants.Select(g => g.NodeAclId).ShouldBe(["acl-cluster", "acl-equipment"], ignoreOrder: true);
|
||||
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ public sealed class OtOpcUaServerHostedServiceStartFailureTests
|
||||
state.Status.ShouldBe(OpcUaEndpointStatus.Failed);
|
||||
state.FailureReason.ShouldNotBeNullOrWhiteSpace();
|
||||
|
||||
var probe = await new OpcUaEndpointHealthCheck(state).CheckHealthAsync(new HealthCheckContext());
|
||||
var probe = await new OpcUaEndpointHealthCheck(state).CheckHealthAsync(new HealthCheckContext(), TestContext.Current.CancellationToken);
|
||||
probe.Status.ShouldBe(HealthStatus.Unhealthy);
|
||||
probe.Description.ShouldNotBeNull();
|
||||
probe.Description.ShouldContain("failed to start");
|
||||
|
||||
@@ -29,8 +29,8 @@ public sealed class FailStartExitTests
|
||||
psi.Environment["Cluster__SeedNodes__0"] = "not-a-valid-address";
|
||||
|
||||
using var proc = Process.Start(psi)!;
|
||||
var stdout = proc.StandardOutput.ReadToEndAsync();
|
||||
var stderr = proc.StandardError.ReadToEndAsync();
|
||||
var stdout = proc.StandardOutput.ReadToEndAsync(TestContext.Current.CancellationToken);
|
||||
var stderr = proc.StandardError.ReadToEndAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(60));
|
||||
try { await proc.WaitForExitAsync(cts.Token); }
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit"/>
|
||||
<PackageReference Include="xunit.v3" VersionOverride="3.2.2"/>
|
||||
<PackageReference Include="Shouldly"/>
|
||||
<!-- NodeCommandGrpcServiceTests registers a TestProbe under NodeCommunicationActorKey, so the
|
||||
service is asserted against the same ActorRegistry seam the Host wires in production. -->
|
||||
<PackageReference Include="Akka.TestKit.Xunit2"/>
|
||||
<PackageReference Include="Akka.TestKit.Xunit"/>
|
||||
<!-- ConfigDbApiKeyStoreTests drives the store against a real EF query, the same in-memory
|
||||
provider convention the ControlPlane/Runtime test projects use. No central SQL Server. -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory"/>
|
||||
|
||||
+6
-6
@@ -1,5 +1,5 @@
|
||||
using Akka.Actor;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Akka.TestKit.Xunit;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
|
||||
@@ -56,7 +56,7 @@ public class EventStreamCommandDeliveryTests : RuntimeActorTestBase
|
||||
// EventStream subscription. Without this the relay below races the subscription.
|
||||
var warmup = NewDispatch();
|
||||
driverHost.Tell(warmup);
|
||||
ackRouter.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(15)).DeploymentId.ShouldBe(warmup.DeploymentId);
|
||||
ackRouter.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(15), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(warmup.DeploymentId);
|
||||
|
||||
// Now the real assertion — relayed exactly as central would send it. The comm actor only
|
||||
// publishes node-locally, so the host must be subscribed for this to arrive at all.
|
||||
@@ -66,7 +66,7 @@ public class EventStreamCommandDeliveryTests : RuntimeActorTestBase
|
||||
// The deployment id does not exist, so the host answers Failed. That is the stronger
|
||||
// assertion, not a weaker one: an ack at all proves the command was received and handled,
|
||||
// and only PreStart's EventStream subscription can have delivered it.
|
||||
ackRouter.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(15)).DeploymentId.ShouldBe(relayed.DeploymentId);
|
||||
ackRouter.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(15), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(relayed.DeploymentId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -83,7 +83,7 @@ public class EventStreamCommandDeliveryTests : RuntimeActorTestBase
|
||||
var dispatch = NewDispatch();
|
||||
driverHost.Tell(dispatch);
|
||||
|
||||
ackRouter.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(15)).DeploymentId.ShouldBe(dispatch.DeploymentId);
|
||||
ackRouter.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(15), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(dispatch.DeploymentId);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -150,12 +150,12 @@ akka {
|
||||
// Tell INSIDE the assertion window alongside the relay, which made the test vacuous — the
|
||||
// direct Tell alone produced the log the assertion was waiting for.
|
||||
EventFilter.Debug(contains: "ignoring AlarmCommand")
|
||||
.ExpectOne(TimeSpan.FromSeconds(15), () => host.Tell(cmd));
|
||||
.ExpectOne(TimeSpan.FromSeconds(15), () => host.Tell(cmd), TestContext.Current.CancellationToken);
|
||||
|
||||
// The real assertion: relayed ONLY through the comm actor, which publishes node-locally.
|
||||
// The host owns no alarms, so it logs and stops — still a positive delivery signal, because
|
||||
// that line is only reached if the message arrived.
|
||||
EventFilter.Debug(contains: "ignoring AlarmCommand")
|
||||
.ExpectOne(TimeSpan.FromSeconds(15), () => comm.Tell(cmd));
|
||||
.ExpectOne(TimeSpan.FromSeconds(15), () => comm.Tell(cmd), TestContext.Current.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -73,7 +73,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
return (CentralCommandService.CentralCommandServiceClient)new RecordingClient(calls);
|
||||
};
|
||||
|
||||
await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack());
|
||||
await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack(), TestContext.Current.CancellationToken);
|
||||
|
||||
consulted.ShouldBe(["http://central-1:4055"]);
|
||||
calls.Count.ShouldBe(1);
|
||||
@@ -90,7 +90,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
"disk full",
|
||||
new CorrelationId(Guid.NewGuid()));
|
||||
|
||||
await Sender(["http://central-1:4055"], _ => new RecordingClient(calls)).SendAsync(ack);
|
||||
await Sender(["http://central-1:4055"], _ => new RecordingClient(calls)).SendAsync(ack, TestContext.Current.CancellationToken);
|
||||
|
||||
var sent = calls.Single().Request;
|
||||
sent.DeploymentId.ShouldBe(ack.DeploymentId.ToString());
|
||||
@@ -109,7 +109,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
var calls = new List<(ApplyAckMessage Request, CallOptions Options)>();
|
||||
var before = DateTime.UtcNow;
|
||||
|
||||
await Sender(["http://central-1:4055"], _ => new RecordingClient(calls)).SendAsync(Ack());
|
||||
await Sender(["http://central-1:4055"], _ => new RecordingClient(calls)).SendAsync(Ack(), TestContext.Current.CancellationToken);
|
||||
|
||||
var options = calls.Single().Options;
|
||||
options.Headers.ShouldNotBeNull();
|
||||
@@ -138,7 +138,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
? (CentralCommandService.CentralCommandServiceClient)new SlowThrowingClient(calls, TimeSpan.FromMilliseconds(50))
|
||||
: new RecordingClient(calls);
|
||||
|
||||
await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack());
|
||||
await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack(), TestContext.Current.CancellationToken);
|
||||
|
||||
calls.Count.ShouldBe(2);
|
||||
calls[1].Options.Deadline!.Value.ShouldBeGreaterThan(calls[0].Options.Deadline!.Value);
|
||||
@@ -158,7 +158,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
: new RecordingClient(calls);
|
||||
};
|
||||
|
||||
await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack());
|
||||
await Sender(["http://central-1:4055", "http://central-2:4055"], factory).SendAsync(Ack(), TestContext.Current.CancellationToken);
|
||||
|
||||
consulted.ShouldBe(["http://central-1:4055", "http://central-2:4055"]);
|
||||
calls.Count.ShouldBe(1);
|
||||
@@ -185,7 +185,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
};
|
||||
var ack = Ack();
|
||||
|
||||
await Sender(["http://central-1:4055", "http://central-2:4055"], factory, log).SendAsync(ack);
|
||||
await Sender(["http://central-1:4055", "http://central-2:4055"], factory, log).SendAsync(ack, TestContext.Current.CancellationToken);
|
||||
|
||||
attempts.ShouldBe(2);
|
||||
var warning = log.Entries.Single(e => e.Level == LogLevel.Warning);
|
||||
@@ -213,7 +213,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
: new RecordingClient(calls);
|
||||
};
|
||||
|
||||
await Sender(["http://c1:4055", "http://c2:4055", "http://c3:4055"], factory).SendAsync(Ack());
|
||||
await Sender(["http://c1:4055", "http://c2:4055", "http://c3:4055"], factory).SendAsync(Ack(), TestContext.Current.CancellationToken);
|
||||
|
||||
// c3 is never dialled: the pass stops at the first success, and it does not go back to c1.
|
||||
consulted.ShouldBe(["http://c1:4055", "http://c2:4055"]);
|
||||
@@ -230,7 +230,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
var factory = (string _) => (CentralCommandService.CentralCommandServiceClient)
|
||||
new ThrowingClient(new RpcException(new Status(StatusCode.Unavailable, "down")));
|
||||
|
||||
await Sender(["http://central-1:4055"], factory, log).SendAsync(Ack());
|
||||
await Sender(["http://central-1:4055"], factory, log).SendAsync(Ack(), TestContext.Current.CancellationToken);
|
||||
|
||||
log.Entries.ShouldNotBeEmpty();
|
||||
log.Entries.Any(e => e.Message.Contains(Psk, StringComparison.Ordinal)).ShouldBeFalse();
|
||||
@@ -251,7 +251,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
return (CentralCommandService.CentralCommandServiceClient)new RecordingClient([]);
|
||||
};
|
||||
|
||||
await Sender([], factory, log).SendAsync(Ack());
|
||||
await Sender([], factory, log).SendAsync(Ack(), TestContext.Current.CancellationToken);
|
||||
|
||||
dialled.ShouldBeFalse();
|
||||
log.Entries.Single(e => e.Level == LogLevel.Warning).Message
|
||||
@@ -269,7 +269,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
var calls = new List<(ApplyAckMessage Request, CallOptions Options)>();
|
||||
|
||||
await Sender(["http://central-1:4055"], _ => new RecordingClient(calls), log, roles: ["driver"])
|
||||
.SendAsync(Ack());
|
||||
.SendAsync(Ack(), TestContext.Current.CancellationToken);
|
||||
|
||||
calls.Single().Options.Headers!.Any(h =>
|
||||
h.Key.Equals("x-otopcua-cluster", StringComparison.Ordinal)).ShouldBeFalse();
|
||||
@@ -311,7 +311,7 @@ public sealed class GrpcApplyAckSenderTests
|
||||
Options.Create(new AkkaClusterOptions { Roles = ["driver", "cluster-SITE-A"] }),
|
||||
log);
|
||||
|
||||
await sender.SendAsync(Ack());
|
||||
await sender.SendAsync(Ack(), TestContext.Current.CancellationToken);
|
||||
|
||||
// The post must have failed (nothing is listening) — which is what put a channel in the cache.
|
||||
log.Entries.Any(e => e.Level == LogLevel.Warning).ShouldBeTrue();
|
||||
|
||||
+10
-10
@@ -28,7 +28,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
|
||||
var dispatch = NewDispatch();
|
||||
actor.Tell(dispatch);
|
||||
|
||||
subscriber.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(5)).ShouldBe(dispatch);
|
||||
subscriber.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(dispatch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -43,8 +43,8 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
|
||||
|
||||
actor.Tell(NewDispatch());
|
||||
|
||||
subscriber.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(5));
|
||||
subscriber.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
subscriber.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
subscriber.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -57,7 +57,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
|
||||
var msg = new RestartDriver("C1", "driver-1", "alice", Guid.NewGuid());
|
||||
actor.Tell(msg);
|
||||
|
||||
subscriber.ExpectMsg<RestartDriver>(TimeSpan.FromSeconds(5)).ShouldBe(msg);
|
||||
subscriber.ExpectMsg<RestartDriver>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(msg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -70,7 +70,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
|
||||
var msg = new ReconnectDriver("C1", "driver-1", "alice", Guid.NewGuid());
|
||||
actor.Tell(msg);
|
||||
|
||||
subscriber.ExpectMsg<ReconnectDriver>(TimeSpan.FromSeconds(5)).ShouldBe(msg);
|
||||
subscriber.ExpectMsg<ReconnectDriver>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(msg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -86,7 +86,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
|
||||
var msg = new AlarmCommand("alarm-1", "Acknowledge", "alice", "comment", null);
|
||||
actor.Tell(msg);
|
||||
|
||||
subscriber.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(5)).ShouldBe(msg);
|
||||
subscriber.ExpectMsg<AlarmCommand>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(msg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -104,7 +104,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
|
||||
new CorrelationId(Guid.NewGuid()));
|
||||
|
||||
EventFilter.Warning(contains: "dropping ApplyAck").ExpectOne(
|
||||
TimeSpan.FromSeconds(5), () => actor.Tell(ack));
|
||||
TimeSpan.FromSeconds(5), () => actor.Tell(ack), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
// ---- Mode=Grpc (the cross-mesh ack leg) -----------------------------------------------------
|
||||
@@ -129,9 +129,9 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
|
||||
var ack = NewAck();
|
||||
actor.Tell(ack);
|
||||
|
||||
AwaitAssert(() => sender.Sent.Count.ShouldBe(1), TimeSpan.FromSeconds(5));
|
||||
AwaitAssert(() => sender.Sent.Count.ShouldBe(1), TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
sender.Sent[0].ShouldBe(ack);
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
sender.Sent.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ public class NodeCommunicationActorTests : RuntimeActorTestBase
|
||||
var dispatch = NewDispatch();
|
||||
actor.Tell(dispatch);
|
||||
|
||||
subscriber.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(5)).ShouldBe(dispatch);
|
||||
subscriber.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).ShouldBe(dispatch);
|
||||
sender.Sent.ShouldBeEmpty();
|
||||
}
|
||||
|
||||
|
||||
+37
-37
@@ -119,9 +119,9 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
var artifact = RandomArtifact(300 * 1024);
|
||||
var cache = CreateCache();
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken);
|
||||
|
||||
var cached = await cache.GetCurrentAsync(ClusterA);
|
||||
var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken);
|
||||
|
||||
cached.ShouldNotBeNull();
|
||||
cached.DeploymentId.ShouldBe("dep-1");
|
||||
@@ -135,15 +135,15 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
{
|
||||
var cache = CreateCache();
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(64));
|
||||
await cache.StoreAsync(ClusterA, "dep-2", "rev-2", RandomArtifact(64));
|
||||
await cache.StoreAsync(ClusterA, "dep-3", "rev-3", RandomArtifact(64));
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(64), TestContext.Current.CancellationToken);
|
||||
await cache.StoreAsync(ClusterA, "dep-2", "rev-2", RandomArtifact(64), TestContext.Current.CancellationToken);
|
||||
await cache.StoreAsync(ClusterA, "dep-3", "rev-3", RandomArtifact(64), TestContext.Current.CancellationToken);
|
||||
|
||||
// Bounded retention is what stops a node that redeploys daily from filling its disk with
|
||||
// address spaces nobody will ever roll back to.
|
||||
(await DistinctDeploymentIdsAsync()).ShouldBe(["dep-2", "dep-3"]);
|
||||
|
||||
var cached = await cache.GetCurrentAsync(ClusterA);
|
||||
var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken);
|
||||
cached.ShouldNotBeNull();
|
||||
cached.DeploymentId.ShouldBe("dep-3");
|
||||
}
|
||||
@@ -163,20 +163,20 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
var cache = CreateCache();
|
||||
var newest = RandomArtifact(1024);
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-old-1", "rev-1", RandomArtifact(1024));
|
||||
await cache.StoreAsync(ClusterA, "dep-old-2", "rev-2", RandomArtifact(1024));
|
||||
await cache.StoreAsync(ClusterA, "dep-old-1", "rev-1", RandomArtifact(1024), TestContext.Current.CancellationToken);
|
||||
await cache.StoreAsync(ClusterA, "dep-old-2", "rev-2", RandomArtifact(1024), TestContext.Current.CancellationToken);
|
||||
|
||||
await _db.ExecuteAsync(
|
||||
"UPDATE deployment_artifacts SET cached_at_utc = @Future",
|
||||
new { Future = "2099-01-01T00:00:00.0000000Z" });
|
||||
new { Future = "2099-01-01T00:00:00.0000000Z" }, TestContext.Current.CancellationToken);
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-new", "rev-3", newest);
|
||||
await cache.StoreAsync(ClusterA, "dep-new", "rev-3", newest, TestContext.Current.CancellationToken);
|
||||
|
||||
// The pointer names dep-new, so dep-new's chunks must still be there...
|
||||
(await CountChunksAsync("dep-new")).ShouldBeGreaterThan(0);
|
||||
|
||||
// ...and it must actually read back, which is the behaviour that matters.
|
||||
var cached = await cache.GetCurrentAsync(ClusterA);
|
||||
var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken);
|
||||
cached.ShouldNotBeNull();
|
||||
cached.DeploymentId.ShouldBe("dep-new");
|
||||
cached.Artifact.ShouldBe(newest);
|
||||
@@ -186,31 +186,31 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
public async Task GetCurrent_ReturnsNullWhenAChunkIsCorrupt()
|
||||
{
|
||||
var cache = CreateCache();
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024));
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024), TestContext.Current.CancellationToken);
|
||||
|
||||
// Valid base64 of the wrong bytes: this passes decoding and the chunk-count check, so only
|
||||
// the SHA-256 comparison can catch it. That is the check being pinned.
|
||||
await _db.ExecuteAsync(
|
||||
"UPDATE deployment_artifacts SET chunk_base64 = @Chunk WHERE deployment_id = @DeploymentId AND chunk_index = 1",
|
||||
new { Chunk = Convert.ToBase64String(RandomArtifact(128 * 1024)), DeploymentId = "dep-1" });
|
||||
new { Chunk = Convert.ToBase64String(RandomArtifact(128 * 1024)), DeploymentId = "dep-1" }, TestContext.Current.CancellationToken);
|
||||
|
||||
(await cache.GetCurrentAsync(ClusterA)).ShouldBeNull();
|
||||
(await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetCurrent_ReturnsNullWhenAChunkIsMissing()
|
||||
{
|
||||
var cache = CreateCache();
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024));
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024), TestContext.Current.CancellationToken);
|
||||
|
||||
// A partially replicated artifact is the realistic version of this: the pointer row arrives
|
||||
// before the last chunk does. Reassembling what is present would yield a plausible-looking
|
||||
// but silently truncated address space.
|
||||
await _db.ExecuteAsync(
|
||||
"DELETE FROM deployment_artifacts WHERE deployment_id = @DeploymentId AND chunk_index = 2",
|
||||
new { DeploymentId = "dep-1" });
|
||||
new { DeploymentId = "dep-1" }, TestContext.Current.CancellationToken);
|
||||
|
||||
(await cache.GetCurrentAsync(ClusterA)).ShouldBeNull();
|
||||
(await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -218,7 +218,7 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
{
|
||||
var cache = CreateCache();
|
||||
|
||||
(await cache.GetCurrentAsync(ClusterA)).ShouldBeNull();
|
||||
(await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -226,17 +226,17 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
{
|
||||
var cache = CreateCache();
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024));
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(300 * 1024), TestContext.Current.CancellationToken);
|
||||
|
||||
// The re-store is smaller, so a delete-before-insert that did not happen would leave the
|
||||
// tail chunks of the first artifact behind — orphans that also break the chunk-count check.
|
||||
var second = RandomArtifact(200 * 1024);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1b", second);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1b", second, TestContext.Current.CancellationToken);
|
||||
|
||||
(await CountChunksAsync("dep-1")).ShouldBe(2);
|
||||
(await CountChunksAsync()).ShouldBe(2);
|
||||
|
||||
var cached = await cache.GetCurrentAsync(ClusterA);
|
||||
var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken);
|
||||
cached.ShouldNotBeNull();
|
||||
cached.RevisionHash.ShouldBe("rev-1b");
|
||||
cached.Artifact.ShouldBe(second);
|
||||
@@ -253,18 +253,18 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
var cache = CreateCache();
|
||||
var artifact = RandomArtifact(300 * 1024);
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken);
|
||||
|
||||
var oplogDepth = await OplogDepthAsync();
|
||||
var rowVersions = await RowVersionsAsync();
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken);
|
||||
|
||||
(await OplogDepthAsync()).ShouldBe(oplogDepth);
|
||||
(await RowVersionsAsync()).ShouldBe(rowVersions);
|
||||
|
||||
// Skipping must not cost readability: the artifact still reads back intact.
|
||||
var cached = await cache.GetCurrentAsync(ClusterA);
|
||||
var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken);
|
||||
cached.ShouldNotBeNull();
|
||||
cached.DeploymentId.ShouldBe("dep-1");
|
||||
cached.Artifact.ShouldBe(artifact);
|
||||
@@ -280,16 +280,16 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
var cache = CreateCache();
|
||||
var artifact = RandomArtifact(300 * 1024);
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken);
|
||||
await _db.ExecuteAsync(
|
||||
"DELETE FROM deployment_artifacts WHERE deployment_id = @DeploymentId AND chunk_index = 2",
|
||||
new { DeploymentId = "dep-1" });
|
||||
(await cache.GetCurrentAsync(ClusterA)).ShouldBeNull();
|
||||
new { DeploymentId = "dep-1" }, TestContext.Current.CancellationToken);
|
||||
(await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken)).ShouldBeNull();
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken);
|
||||
|
||||
(await CountChunksAsync("dep-1")).ShouldBe(3);
|
||||
var cached = await cache.GetCurrentAsync(ClusterA);
|
||||
var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken);
|
||||
cached.ShouldNotBeNull();
|
||||
cached.Artifact.ShouldBe(artifact);
|
||||
}
|
||||
@@ -303,12 +303,12 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
// space with every id-shaped signal saying it was correct.
|
||||
var cache = CreateCache();
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(1024));
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", RandomArtifact(1024), TestContext.Current.CancellationToken);
|
||||
|
||||
var rebuilt = RandomArtifact(1024);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", rebuilt);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", rebuilt, TestContext.Current.CancellationToken);
|
||||
|
||||
var cached = await cache.GetCurrentAsync(ClusterA);
|
||||
var cached = await cache.GetCurrentAsync(ClusterA, TestContext.Current.CancellationToken);
|
||||
cached.ShouldNotBeNull();
|
||||
cached.Artifact.ShouldBe(rebuilt);
|
||||
}
|
||||
@@ -319,9 +319,9 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
var artifact = RandomArtifact(300 * 1024);
|
||||
var cache = CreateCache();
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact);
|
||||
await cache.StoreAsync(ClusterA, "dep-1", "rev-1", artifact, TestContext.Current.CancellationToken);
|
||||
|
||||
var cached = await cache.GetCurrentUnkeyedAsync();
|
||||
var cached = await cache.GetCurrentUnkeyedAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
cached.ShouldNotBeNull();
|
||||
cached.DeploymentId.ShouldBe("dep-1");
|
||||
@@ -335,10 +335,10 @@ public sealed class LocalDbDeploymentArtifactCacheTests : IDisposable
|
||||
var logger = new CapturingLogger();
|
||||
var cache = CreateCache(logger);
|
||||
|
||||
await cache.StoreAsync(ClusterA, "dep-a", "rev-a", RandomArtifact(1024));
|
||||
await cache.StoreAsync(ClusterB, "dep-b", "rev-b", newest);
|
||||
await cache.StoreAsync(ClusterA, "dep-a", "rev-a", RandomArtifact(1024), TestContext.Current.CancellationToken);
|
||||
await cache.StoreAsync(ClusterB, "dep-b", "rev-b", newest, TestContext.Current.CancellationToken);
|
||||
|
||||
var cached = await cache.GetCurrentUnkeyedAsync();
|
||||
var cached = await cache.GetCurrentUnkeyedAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
cached.ShouldNotBeNull();
|
||||
cached.DeploymentId.ShouldBe("dep-b");
|
||||
|
||||
+2
-2
@@ -100,7 +100,7 @@ public sealed class AbCipAlmaNativeAlarmEndToEndTests : RuntimeActorTestBase
|
||||
drop.State.Message.ShouldContain("H alarm");
|
||||
drop.State.Message.ShouldNotContain("HH");
|
||||
drop.State.Severity.ShouldBe((ushort)600);
|
||||
alerts.ExpectNoMsg(Quiet);
|
||||
alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken);
|
||||
|
||||
// The PLC acknowledges every declared limit — ONE "Acknowledged" row.
|
||||
foreach (var a in new[] { "HHAcked", "HAcked", "LAcked", "LLAcked" }) plc.Set(a, true);
|
||||
@@ -116,7 +116,7 @@ public sealed class AbCipAlmaNativeAlarmEndToEndTests : RuntimeActorTestBase
|
||||
plc.Set("HHInAlarm", false);
|
||||
var back = NextUpdate(publish);
|
||||
back.State.Acknowledged.ShouldBeFalse("the unacknowledged HH excursion keeps the condition unacknowledged");
|
||||
alerts.ExpectNoMsg(Quiet);
|
||||
alerts.ExpectNoMsg(Quiet, TestContext.Current.CancellationToken);
|
||||
|
||||
// Positive control: the clear MUST write its row (the absence checks above would pass on a dead host).
|
||||
plc.Set("HInAlarm", false);
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class ActorNodeWriteGatewayTests : RuntimeActorTestBase
|
||||
|
||||
var writeTask = gateway.WriteAsync("eq-1/speed", 123.0, AddressSpaceRealm.Uns, CancellationToken.None);
|
||||
|
||||
var routed = probe.ExpectMsg<DriverHostActor.RouteNodeWrite>(TimeSpan.FromSeconds(5));
|
||||
var routed = probe.ExpectMsg<DriverHostActor.RouteNodeWrite>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
routed.NodeId.ShouldBe("eq-1/speed");
|
||||
routed.Value.ShouldBe(123.0);
|
||||
probe.Reply(new DriverHostActor.NodeWriteResult(true, null));
|
||||
@@ -46,7 +46,7 @@ public sealed class ActorNodeWriteGatewayTests : RuntimeActorTestBase
|
||||
|
||||
var writeTask = gateway.WriteAsync("eq-1/speed", 123.0, AddressSpaceRealm.Uns, CancellationToken.None);
|
||||
|
||||
probe.ExpectMsg<DriverHostActor.RouteNodeWrite>(TimeSpan.FromSeconds(5));
|
||||
probe.ExpectMsg<DriverHostActor.RouteNodeWrite>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
probe.Reply(new DriverHostActor.NodeWriteResult(false, "not primary"));
|
||||
|
||||
var outcome = await writeTask;
|
||||
|
||||
+4
-4
@@ -82,7 +82,7 @@ public sealed class CalculationDependencyFlowTests : RuntimeActorTestBase
|
||||
published.ShouldContain(e => e.FullReference == "calc/engine/out" && Equals(e.Snapshot.Value, 42.0));
|
||||
// Leg 2 — calc-of-calc: A's 42 re-entered the mux → B computes 43 on calc/engine/b.
|
||||
published.ShouldContain(e => e.FullReference == "calc/engine/b" && Equals(e.Snapshot.Value, 43.0));
|
||||
}, PresenceBudget, TimeSpan.FromMilliseconds(100));
|
||||
}, PresenceBudget, TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -128,7 +128,7 @@ public sealed class CalculationDependencyFlowTests : RuntimeActorTestBase
|
||||
mux.Tell(ValuePublishes.Single("src-drv", "src/a", 2.0, OpcUaStatus.Good, Ts));
|
||||
published.ShouldContain(e => e.FullReference == "calc/keep" && Equals(e.Snapshot.Value, 20.0));
|
||||
published.ShouldContain(e => e.FullReference == "calc/remove" && Equals(e.Snapshot.Value, 7.0));
|
||||
}, PresenceBudget, TimeSpan.FromMilliseconds(100));
|
||||
}, PresenceBudget, TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken);
|
||||
|
||||
// ---- IN-PLACE RE-BIND (driver ReinitializeAsync) ----
|
||||
// New authored set: "keep" script EDITED to src/a*1000; "add" is a NEW tag reading a NEW dep src/b;
|
||||
@@ -157,11 +157,11 @@ public sealed class CalculationDependencyFlowTests : RuntimeActorTestBase
|
||||
published.ShouldContain(e => e.FullReference == "calc/add" && Equals(e.Snapshot.Value, 150.0));
|
||||
// (c) edited script produces the NEW result: 3 * 1000 = 3000 (NOT the pre-edit 3 * 10 = 30).
|
||||
published.ShouldContain(e => e.FullReference == "calc/keep" && Equals(e.Snapshot.Value, 3000.0));
|
||||
}, PresenceBudget, TimeSpan.FromMilliseconds(100));
|
||||
}, PresenceBudget, TimeSpan.FromMilliseconds(100), TestContext.Current.CancellationToken);
|
||||
|
||||
// (b) the removed tag never recomputes on the post-redeploy src/a=3 (it would be 3 + 5 = 8) and the
|
||||
// edited "keep" never serves the stale *10 result (3 * 10 = 30). Give any errant compute a moment to land.
|
||||
await Task.Delay(200);
|
||||
await Task.Delay(200, TestContext.Current.CancellationToken);
|
||||
published.ShouldNotContain(e => e.FullReference == "calc/remove" && Equals(e.Snapshot.Value, 8.0));
|
||||
published.ShouldNotContain(e => e.FullReference == "calc/keep" && Equals(e.Snapshot.Value, 30.0));
|
||||
}
|
||||
|
||||
+15
-15
@@ -67,7 +67,7 @@ public sealed class DriverHostActorApplyingStashTests()
|
||||
{
|
||||
// A is in flight: the fetch has started and its task will not complete until released.
|
||||
actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId()));
|
||||
AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget);
|
||||
AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logs = SubscribeToLogs("deferring");
|
||||
|
||||
@@ -86,8 +86,8 @@ public sealed class DriverHostActorApplyingStashTests()
|
||||
|
||||
// Nothing was lost: B re-enters exactly once, after A's apply transitions back to Steady.
|
||||
fetcher.Release();
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
fetcher.Calls.ShouldBe(2);
|
||||
}
|
||||
finally
|
||||
@@ -137,7 +137,7 @@ public sealed class DriverHostActorApplyingStashTests()
|
||||
try
|
||||
{
|
||||
actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId()));
|
||||
AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget);
|
||||
AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logs = SubscribeToLogs("deferring", "already deferred");
|
||||
|
||||
@@ -152,9 +152,9 @@ public sealed class DriverHostActorApplyingStashTests()
|
||||
|
||||
// No second stash, therefore no second re-delivery: exactly one Applied ack for B.
|
||||
fetcher.Release();
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
fetcher.Calls.ShouldBe(2);
|
||||
}
|
||||
finally
|
||||
@@ -194,24 +194,24 @@ public sealed class DriverHostActorApplyingStashTests()
|
||||
try
|
||||
{
|
||||
actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId()));
|
||||
AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget);
|
||||
AwaitAssert(() => fetcher.Calls.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
actor.Tell(new DispatchDeployment(deploymentB, RevB, CorrelationId.NewId()));
|
||||
|
||||
// A completes; B unstashes and starts its own (still gated) fetch.
|
||||
fetcher.Release(1);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10)).DeploymentId.ShouldBe(deploymentA);
|
||||
AwaitAssert(() => fetcher.Calls.ShouldBe(2), duration: PresenceBudget);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(deploymentA);
|
||||
AwaitAssert(() => fetcher.Calls.ShouldBe(2), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// C arrives mid-B — a fresh stash cycle, on a set that was cleared when B was released.
|
||||
actor.Tell(new DispatchDeployment(deploymentC, RevC, CorrelationId.NewId()));
|
||||
|
||||
fetcher.Release(2);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10)).DeploymentId.ShouldBe(deploymentB);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10)).DeploymentId.ShouldBe(deploymentC);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(deploymentB);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).DeploymentId.ShouldBe(deploymentC);
|
||||
|
||||
actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref);
|
||||
coordinator.ExpectMsg<NodeDiagnosticsSnapshot>(TimeSpan.FromSeconds(5))
|
||||
coordinator.ExpectMsg<NodeDiagnosticsSnapshot>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken)
|
||||
.CurrentRevision.ShouldBe(RevC);
|
||||
}
|
||||
finally
|
||||
@@ -251,7 +251,7 @@ public sealed class DriverHostActorApplyingStashTests()
|
||||
try
|
||||
{
|
||||
EventFilter.Warning(contains: deploymentA.ToString()).ExpectOne(TimeSpan.FromSeconds(10), () =>
|
||||
actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId())));
|
||||
actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId())), TestContext.Current.CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -285,7 +285,7 @@ public sealed class DriverHostActorApplyingStashTests()
|
||||
|
||||
var logs = SubscribeToLogs("appears WEDGED");
|
||||
actor.Tell(new DispatchDeployment(deploymentA, RevA, CorrelationId.NewId()));
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(10), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
|
||||
// Well past the (200 ms) delay — a live timer would have fired several times over.
|
||||
logs.Drain(SpinWindow);
|
||||
|
||||
+10
-10
@@ -42,9 +42,9 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase
|
||||
deploymentArtifactCache: cache));
|
||||
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
|
||||
AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3));
|
||||
AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var store = cache.Stores[0];
|
||||
store.DeploymentId.ShouldBe(deploymentId.ToString());
|
||||
@@ -69,9 +69,9 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase
|
||||
deploymentArtifactCache: cache));
|
||||
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5));
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3));
|
||||
AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
cache.Stores[0].ClusterId.ShouldBe("SITE-A");
|
||||
}
|
||||
|
||||
@@ -89,9 +89,9 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase
|
||||
deploymentArtifactCache: cache));
|
||||
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5));
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3));
|
||||
AwaitAssert(() => cache.Stores.Count.ShouldBe(1), duration: TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
cache.Stores[0].ClusterId.ShouldBe("__single");
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase
|
||||
// Since #486 the apply FAILS rather than reporting a success it did not achieve: empty bytes are
|
||||
// what an unreadable artifact looks like, not a legitimate no-op deployment. (A configuration that
|
||||
// genuinely declares nothing still serialises to a JSON document with empty arrays.)
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Failed);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Failed);
|
||||
|
||||
// The point of this test is unchanged, and now holds for two independent reasons: nothing is cached.
|
||||
Thread.Sleep(500);
|
||||
@@ -156,11 +156,11 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase
|
||||
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
|
||||
// Exactly one ACK, and it says Applied. A second Failed ACK arriving here would mean the
|
||||
// cache failure had corrupted the deployment protocol.
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -177,7 +177,7 @@ public sealed class DriverHostActorArtifactCacheTests : RuntimeActorTestBase
|
||||
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+6
-6
@@ -56,21 +56,21 @@ public sealed class DriverHostActorAsyncCacheWriteTests()
|
||||
try
|
||||
{
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5), cancellationToken: TestContext.Current.CancellationToken).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
|
||||
// The store was entered and has NOT completed — the gate is still closed.
|
||||
AwaitAssert(() => cache.StoresEntered.ShouldBe(1), duration: PresenceBudget);
|
||||
AwaitAssert(() => cache.StoresEntered.ShouldBe(1), duration: PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
cache.StoresCompleted.ShouldBe(0);
|
||||
|
||||
// THE assertion: with the store still in flight the host is still processing its mailbox.
|
||||
// A blocking `.GetAwaiter().GetResult()` parks the actor thread here and this times out.
|
||||
actor.Tell(new GetDiagnostics(CorrelationId.NewId()), coordinator.Ref);
|
||||
coordinator.ExpectMsg<NodeDiagnosticsSnapshot>(TimeSpan.FromSeconds(3));
|
||||
coordinator.ExpectMsg<NodeDiagnosticsSnapshot>(TimeSpan.FromSeconds(3), cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Completion is observed rather than fire-and-forgotten: the store pipes back to Self and
|
||||
// the handler logs it, so an operator can tell a cached apply from an uncached one.
|
||||
EventFilter.Info(contains: "cached applied deployment")
|
||||
.ExpectOne(TimeSpan.FromSeconds(5), () => cache.Release());
|
||||
.ExpectOne(TimeSpan.FromSeconds(5), () => cache.Release(), TestContext.Current.CancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -107,9 +107,9 @@ public sealed class DriverHostActorAsyncCacheWriteTests()
|
||||
{
|
||||
actor.Tell(new DispatchDeployment(deploymentId, RevA, CorrelationId.NewId()));
|
||||
coordinator.ExpectMsg<ApplyAck>(TimeSpan.FromSeconds(5)).Outcome.ShouldBe(ApplyAckOutcome.Applied);
|
||||
});
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>Seeds a sealed, parseable deployment and returns its id.</summary>
|
||||
|
||||
+9
-9
@@ -57,7 +57,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase
|
||||
new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/temp", 7.5, OpcUaStatus.BadNoCommunication, TsB),
|
||||
}));
|
||||
|
||||
var batch = publish.ExpectMsg<OpcUaPublishActor.AttributeValueBatchUpdate>(PresenceBudget);
|
||||
var batch = publish.ExpectMsg<OpcUaPublishActor.AttributeValueBatchUpdate>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
batch.Items.Count.ShouldBe(3);
|
||||
|
||||
var byId = batch.Items.ToDictionary(w => w.NodeId);
|
||||
@@ -85,7 +85,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase
|
||||
byId["Plant/Modbus/dev1/temp"].Realm.ShouldBe(AddressSpaceRealm.Raw);
|
||||
|
||||
// ONE message, not one per value.
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>An item whose ref is bound to no NodeId is dropped; the rest of the burst still lands in the
|
||||
@@ -106,7 +106,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase
|
||||
new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/speed", 42.0, OpcUaStatus.Good, TsA),
|
||||
}));
|
||||
|
||||
var batch = publish.ExpectMsg<OpcUaPublishActor.AttributeValueBatchUpdate>(PresenceBudget);
|
||||
var batch = publish.ExpectMsg<OpcUaPublishActor.AttributeValueBatchUpdate>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
batch.Items.Count.ShouldBe(1);
|
||||
batch.Items[0].NodeId.ShouldBe("Plant/Modbus/dev1/speed");
|
||||
batch.Items[0].Value.ShouldBe(42.0);
|
||||
@@ -116,7 +116,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase
|
||||
{
|
||||
new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/speed", 43.0, OpcUaStatus.Good, TsB),
|
||||
}));
|
||||
publish.ExpectMsg<OpcUaPublishActor.AttributeValueBatchUpdate>(PresenceBudget)
|
||||
publish.ExpectMsg<OpcUaPublishActor.AttributeValueBatchUpdate>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken)
|
||||
.Items[0].Value.ShouldBe(43.0);
|
||||
}
|
||||
|
||||
@@ -142,11 +142,11 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase
|
||||
new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/temp", 7.5, OpcUaStatus.Good, TsB),
|
||||
}));
|
||||
|
||||
var fwd = mux.ExpectMsg<DriverInstanceActor.AttributeValueBatchPublished>(PresenceBudget);
|
||||
var fwd = mux.ExpectMsg<DriverInstanceActor.AttributeValueBatchPublished>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
fwd.DriverInstanceId.ShouldBe("drv-1");
|
||||
fwd.Items.Select(i => i.FullReference).ShouldBe(
|
||||
new[] { "Plant/Modbus/dev1/speed", "Plant/Modbus/dev1/temp" });
|
||||
mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
mux.ExpectNoMsg(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>A burst in which NOTHING resolves sends no publish message at all — an empty batch would be
|
||||
@@ -168,9 +168,9 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase
|
||||
new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/alsonope", 2.0, OpcUaStatus.Good, TsB),
|
||||
}));
|
||||
|
||||
mux.ExpectMsg<DriverInstanceActor.AttributeValueBatchPublished>(PresenceBudget)
|
||||
mux.ExpectMsg<DriverInstanceActor.AttributeValueBatchPublished>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken)
|
||||
.Items.Count.ShouldBe(2);
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
publish.ExpectNoMsg(TimeSpan.FromMilliseconds(500), TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>A host with NO OPC UA publish actor wired (the ctor default — the dev/Mac shape) still
|
||||
@@ -191,7 +191,7 @@ public sealed class DriverHostActorBatchFanOutTests : RuntimeActorTestBase
|
||||
new DriverInstanceActor.AttributeValueItem("Plant/Modbus/dev1/speed", 42.0, OpcUaStatus.Good, TsA),
|
||||
}));
|
||||
|
||||
var fwd = mux.ExpectMsg<DriverInstanceActor.AttributeValueBatchPublished>(PresenceBudget);
|
||||
var fwd = mux.ExpectMsg<DriverInstanceActor.AttributeValueBatchPublished>(PresenceBudget, cancellationToken: TestContext.Current.CancellationToken);
|
||||
fwd.DriverInstanceId.ShouldBe("drv-1");
|
||||
fwd.Items.Single().FullReference.ShouldBe("Plant/Modbus/dev1/speed");
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user