feat(controlplane): deploy-gate TagConfig strictness — Warn default, Error opt-in at the draft gate (R2-11, 05/CONV-2)
This commit is contained in:
@@ -22,7 +22,7 @@
|
|||||||
{ "id": "T18", "subject": "FOCAS capability-matrix pre-flight on the equipment-tag _parseRef resolve path (BadNodeIdUnknown on rejection)", "status": "completed", "blockedBy": ["T17"] },
|
{ "id": "T18", "subject": "FOCAS capability-matrix pre-flight on the equipment-tag _parseRef resolve path (BadNodeIdUnknown on rejection)", "status": "completed", "blockedBy": ["T17"] },
|
||||||
{ "id": "T19", "subject": "Modbus probe parses the factory DTO shape (timeoutMs round-trip; OpcUaClient parity rule)", "status": "completed", "blockedBy": [] },
|
{ "id": "T19", "subject": "Modbus probe parses the factory DTO shape (timeoutMs round-trip; OpcUaClient parity rule)", "status": "completed", "blockedBy": [] },
|
||||||
{ "id": "T20", "subject": "EquipmentTagConfigInspector DriverType-dispatch map in ControlPlane (+6 Contracts references)", "status": "completed", "blockedBy": ["T12", "T13", "T14", "T15", "T16", "T17"] },
|
{ "id": "T20", "subject": "EquipmentTagConfigInspector DriverType-dispatch map in ControlPlane (+6 Contracts references)", "status": "completed", "blockedBy": ["T12", "T13", "T14", "T15", "T16", "T17"] },
|
||||||
{ "id": "T21", "subject": "AdminOperationsActor deploy-gate wiring + Deployment:TagConfigValidationMode (Warn default | Error opt-in); actor-level consuming test", "status": "pending", "blockedBy": ["T20"] },
|
{ "id": "T21", "subject": "AdminOperationsActor deploy-gate wiring + Deployment:TagConfigValidationMode (Warn default | Error opt-in); actor-level consuming test", "status": "completed", "blockedBy": ["T20"] },
|
||||||
{ "id": "T22", "subject": "AdminUI writable checkbox in the six driver-typed tag editors (models + razor; live /run verify at execution)", "status": "pending", "blockedBy": [] },
|
{ "id": "T22", "subject": "AdminUI writable checkbox in the six driver-typed tag editors (models + razor; live /run verify at execution)", "status": "pending", "blockedBy": [] },
|
||||||
{ "id": "T23", "subject": "Docs + decision record: two-mode rollout, unknown-keys non-goal, FOCAS writable migration note, STATUS.md/00-INDEX.md entries + Phase-C follow-up", "status": "pending", "blockedBy": ["T17", "T21"] },
|
{ "id": "T23", "subject": "Docs + decision record: two-mode rollout, unknown-keys non-goal, FOCAS writable migration note, STATUS.md/00-INDEX.md entries + Phase-C follow-up", "status": "pending", "blockedBy": ["T17", "T21"] },
|
||||||
{ "id": "T24", "subject": "Final sweep: zero parity-comment/ReadEnum-copy grep hits; whole-solution build + test gate", "status": "pending", "blockedBy": ["T5", "T6", "T7", "T8", "T9", "T10", "T12", "T13", "T14", "T15", "T16", "T17", "T18", "T19", "T21", "T22", "T23"] }
|
{ "id": "T24", "subject": "Final sweep: zero parity-comment/ReadEnum-copy grep hits; whole-solution build + test gate", "status": "pending", "blockedBy": ["T5", "T6", "T7", "T8", "T9", "T10", "T12", "T13", "T14", "T15", "T16", "T17", "T18", "T19", "T21", "T22", "T23"] }
|
||||||
|
|||||||
@@ -25,31 +25,37 @@ public sealed class AdminOperationsActor : ReceiveActor
|
|||||||
private readonly IDbContextFactory<OtOpcUaConfigDbContext> _dbFactory;
|
private readonly IDbContextFactory<OtOpcUaConfigDbContext> _dbFactory;
|
||||||
private readonly IActorRef _coordinator;
|
private readonly IActorRef _coordinator;
|
||||||
private readonly IReadOnlyDictionary<string, IDriverProbe> _probesByType;
|
private readonly IReadOnlyDictionary<string, IDriverProbe> _probesByType;
|
||||||
|
private readonly TagConfigValidationMode _tagConfigValidationMode;
|
||||||
private readonly ILoggingAdapter _log = Context.GetLogger();
|
private readonly ILoggingAdapter _log = Context.GetLogger();
|
||||||
|
|
||||||
/// <summary>Creates actor props for the admin operations actor.</summary>
|
/// <summary>Creates actor props for the admin operations actor.</summary>
|
||||||
/// <param name="dbFactory">Factory for creating config database contexts.</param>
|
/// <param name="dbFactory">Factory for creating config database contexts.</param>
|
||||||
/// <param name="coordinator">Reference to the deployment coordinator actor.</param>
|
/// <param name="coordinator">Reference to the deployment coordinator actor.</param>
|
||||||
/// <param name="probes">Driver probes registered in DI; keyed by DriverType (case-insensitive).</param>
|
/// <param name="probes">Driver probes registered in DI; keyed by DriverType (case-insensitive).</param>
|
||||||
|
/// <param name="tagConfigValidationMode">Deploy-gate TagConfig strictness mode (Warn default | Error opt-in).</param>
|
||||||
/// <returns>Props configured to create an AdminOperationsActor.</returns>
|
/// <returns>Props configured to create an AdminOperationsActor.</returns>
|
||||||
public static Props Props(
|
public static Props Props(
|
||||||
IDbContextFactory<OtOpcUaConfigDbContext> dbFactory,
|
IDbContextFactory<OtOpcUaConfigDbContext> dbFactory,
|
||||||
IActorRef coordinator,
|
IActorRef coordinator,
|
||||||
IEnumerable<IDriverProbe> probes) =>
|
IEnumerable<IDriverProbe> probes,
|
||||||
Akka.Actor.Props.Create(() => new AdminOperationsActor(dbFactory, coordinator, probes));
|
TagConfigValidationMode tagConfigValidationMode = TagConfigValidationMode.Warn) =>
|
||||||
|
Akka.Actor.Props.Create(() => new AdminOperationsActor(dbFactory, coordinator, probes, tagConfigValidationMode));
|
||||||
|
|
||||||
/// <summary>Initializes a new instance of the AdminOperationsActor.</summary>
|
/// <summary>Initializes a new instance of the AdminOperationsActor.</summary>
|
||||||
/// <param name="dbFactory">Factory for creating config database contexts.</param>
|
/// <param name="dbFactory">Factory for creating config database contexts.</param>
|
||||||
/// <param name="coordinator">Reference to the deployment coordinator actor.</param>
|
/// <param name="coordinator">Reference to the deployment coordinator actor.</param>
|
||||||
/// <param name="probes">Driver probes registered in DI; keyed by DriverType (case-insensitive).</param>
|
/// <param name="probes">Driver probes registered in DI; keyed by DriverType (case-insensitive).</param>
|
||||||
|
/// <param name="tagConfigValidationMode">Deploy-gate TagConfig strictness mode (Warn default | Error opt-in).</param>
|
||||||
public AdminOperationsActor(
|
public AdminOperationsActor(
|
||||||
IDbContextFactory<OtOpcUaConfigDbContext> dbFactory,
|
IDbContextFactory<OtOpcUaConfigDbContext> dbFactory,
|
||||||
IActorRef coordinator,
|
IActorRef coordinator,
|
||||||
IEnumerable<IDriverProbe> probes)
|
IEnumerable<IDriverProbe> probes,
|
||||||
|
TagConfigValidationMode tagConfigValidationMode = TagConfigValidationMode.Warn)
|
||||||
{
|
{
|
||||||
_dbFactory = dbFactory;
|
_dbFactory = dbFactory;
|
||||||
_coordinator = coordinator;
|
_coordinator = coordinator;
|
||||||
_probesByType = probes.ToDictionary(p => p.DriverType, StringComparer.OrdinalIgnoreCase);
|
_probesByType = probes.ToDictionary(p => p.DriverType, StringComparer.OrdinalIgnoreCase);
|
||||||
|
_tagConfigValidationMode = tagConfigValidationMode;
|
||||||
|
|
||||||
ReceiveAsync<StartDeployment>(HandleStartDeploymentAsync);
|
ReceiveAsync<StartDeployment>(HandleStartDeploymentAsync);
|
||||||
ReceiveAsync<TestDriverConnect>(HandleTestDriverConnectAsync);
|
ReceiveAsync<TestDriverConnect>(HandleTestDriverConnectAsync);
|
||||||
@@ -202,6 +208,31 @@ public sealed class AdminOperationsActor : ReceiveActor
|
|||||||
errors.AddRange(DraftValidator.ValidateClusterTopology(cluster, nodes));
|
errors.AddRange(DraftValidator.ValidateClusterTopology(cluster, nodes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// R2-11 (05/CONV-2): deploy-time TagConfig strictness inspection over equipment tags. The
|
||||||
|
// driver-typed EquipmentTagConfigInspector surfaces present-but-invalid enum values (which the
|
||||||
|
// lenient runtime silently defaults) + structurally-unparseable TagConfig. In Warn mode
|
||||||
|
// (default) these are non-blocking (logged + appended to the accepted result Message); in Error
|
||||||
|
// mode they fold into the reject list so a typo'd config cannot be re-deployed until fixed.
|
||||||
|
// Running servers are untouched either way — the gate only sees re-deploys.
|
||||||
|
var driverTypeById = draft.DriverInstances
|
||||||
|
.GroupBy(d => d.DriverInstanceId, StringComparer.Ordinal)
|
||||||
|
.ToDictionary(g => g.Key, g => g.First().DriverType, StringComparer.Ordinal);
|
||||||
|
var tagConfigWarnings = new List<(string TagId, string Text)>();
|
||||||
|
foreach (var t in draft.Tags.Where(t => t.EquipmentId is not null))
|
||||||
|
{
|
||||||
|
if (!driverTypeById.TryGetValue(t.DriverInstanceId, out var driverType)) continue;
|
||||||
|
foreach (var w in EquipmentTagConfigInspector.Inspect(driverType, t.TagConfig))
|
||||||
|
tagConfigWarnings.Add((t.TagId, $"tag '{t.TagId}' (equipment '{t.EquipmentId}'): {w}"));
|
||||||
|
}
|
||||||
|
if (tagConfigWarnings.Count > 0)
|
||||||
|
{
|
||||||
|
if (_tagConfigValidationMode == TagConfigValidationMode.Error)
|
||||||
|
errors.AddRange(tagConfigWarnings.Select(x => new ValidationError("TagConfigInvalid", x.Text, x.TagId)));
|
||||||
|
else
|
||||||
|
foreach (var x in tagConfigWarnings)
|
||||||
|
_log.Warning("StartDeployment: TagConfig warning (Warn mode, non-blocking): {Warning}", x.Text);
|
||||||
|
}
|
||||||
|
|
||||||
if (errors.Count > 0)
|
if (errors.Count > 0)
|
||||||
{
|
{
|
||||||
var summary = string.Join("; ", errors.Select(e => $"[{e.Code}] {e.Message}"));
|
var summary = string.Join("; ", errors.Select(e => $"[{e.Code}] {e.Message}"));
|
||||||
@@ -277,11 +308,18 @@ public sealed class AdminOperationsActor : ReceiveActor
|
|||||||
|
|
||||||
_coordinator.Tell(new DispatchDeployment(deploymentId, revHash, msg.CorrelationId));
|
_coordinator.Tell(new DispatchDeployment(deploymentId, revHash, msg.CorrelationId));
|
||||||
|
|
||||||
|
// Accepted message = the compile-cost advisory (if any) + any Warn-mode TagConfig warnings, so
|
||||||
|
// the operator sees the non-blocking strictness findings on a successful deploy.
|
||||||
|
var messageParts = new List<string>();
|
||||||
|
if (advisory is not null) messageParts.Add(advisory);
|
||||||
|
messageParts.AddRange(tagConfigWarnings.Select(x => x.Text));
|
||||||
|
var acceptedMessage = messageParts.Count > 0 ? string.Join("; ", messageParts) : null;
|
||||||
|
|
||||||
replyTo.Tell(new StartDeploymentResult(
|
replyTo.Tell(new StartDeploymentResult(
|
||||||
StartDeploymentOutcome.Accepted,
|
StartDeploymentOutcome.Accepted,
|
||||||
deploymentId,
|
deploymentId,
|
||||||
revHash,
|
revHash,
|
||||||
Message: advisory,
|
Message: acceptedMessage,
|
||||||
msg.CorrelationId));
|
msg.CorrelationId));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace ZB.MOM.WW.OtOpcUa.ControlPlane.AdminOperations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How the deploy gate treats TagConfig strictness inspection results (R2-11, 05/CONV-2). Sourced from
|
||||||
|
/// <c>Deployment:TagConfigValidationMode</c> in appsettings; defaults to <see cref="Warn"/> so existing
|
||||||
|
/// deployments are never blocked by a previously-tolerated typo.
|
||||||
|
/// </summary>
|
||||||
|
public enum TagConfigValidationMode
|
||||||
|
{
|
||||||
|
/// <summary>Non-blocking: inspection warnings are logged and appended to the accepted
|
||||||
|
/// <c>StartDeploymentResult.Message</c>, but the deploy still succeeds. The default.</summary>
|
||||||
|
Warn,
|
||||||
|
|
||||||
|
/// <summary>Blocking: inspection warnings fold into the draft-gate reject list, so a config with a
|
||||||
|
/// typo'd enum (or unparseable TagConfig) cannot be re-deployed until fixed. Opt-in per environment
|
||||||
|
/// once the fleet deploys warning-clean.</summary>
|
||||||
|
Error,
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ using Akka.Actor;
|
|||||||
using Akka.Cluster.Hosting;
|
using Akka.Cluster.Hosting;
|
||||||
using Akka.Hosting;
|
using Akka.Hosting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using ZB.MOM.WW.OtOpcUa.Configuration;
|
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||||
using ZB.MOM.WW.OtOpcUa.ControlPlane.AdminOperations;
|
using ZB.MOM.WW.OtOpcUa.ControlPlane.AdminOperations;
|
||||||
@@ -61,7 +62,12 @@ public static class ServiceCollectionExtensions
|
|||||||
var dbFactory = resolver.GetService<IDbContextFactory<OtOpcUaConfigDbContext>>();
|
var dbFactory = resolver.GetService<IDbContextFactory<OtOpcUaConfigDbContext>>();
|
||||||
var coordinator = registry.Get<ConfigPublishCoordinatorKey>();
|
var coordinator = registry.Get<ConfigPublishCoordinatorKey>();
|
||||||
var probes = resolver.GetService<IEnumerable<IDriverProbe>>() ?? Enumerable.Empty<IDriverProbe>();
|
var probes = resolver.GetService<IEnumerable<IDriverProbe>>() ?? Enumerable.Empty<IDriverProbe>();
|
||||||
return AdminOperationsActor.Props(dbFactory, coordinator, probes);
|
// R2-11 (05/CONV-2): deploy-gate TagConfig strictness mode from appsettings
|
||||||
|
// (Deployment:TagConfigValidationMode = Warn default | Error opt-in).
|
||||||
|
var mode = Enum.TryParse<TagConfigValidationMode>(
|
||||||
|
resolver.GetService<IConfiguration>()?["Deployment:TagConfigValidationMode"],
|
||||||
|
ignoreCase: true, out var m) ? m : TagConfigValidationMode.Warn;
|
||||||
|
return AdminOperationsActor.Props(dbFactory, coordinator, probes, mode);
|
||||||
},
|
},
|
||||||
singletonOptions);
|
singletonOptions);
|
||||||
|
|
||||||
|
|||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
using Akka.Actor;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Shouldly;
|
||||||
|
using Xunit;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Admin;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Commons.Messages.Deploy;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration.Entities;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Configuration.Validation;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.ControlPlane.AdminOperations;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.ControlPlane.Tests.Harness;
|
||||||
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||||
|
|
||||||
|
namespace ZB.MOM.WW.OtOpcUa.ControlPlane.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// R2-11 (05/CONV-2) — the deploy gate's TagConfig strictness inspection, exercised THROUGH the actor
|
||||||
|
/// (the F10b/PR#423 lesson: an inspector built but never called is inert). Warn mode (default) accepts
|
||||||
|
/// the deploy with the warning surfaced in the result Message; Error mode rejects the same draft; a
|
||||||
|
/// clean config and an unmapped (Galaxy) tag produce no TagConfig noise.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AdminOperationsActorTagConfigGateTests : ControlPlaneActorTestBase
|
||||||
|
{
|
||||||
|
private static void Seed(IDbContextFactory<OtOpcUaConfigDbContext> dbFactory, string driverType, string tagConfig,
|
||||||
|
string tagName = "flow")
|
||||||
|
{
|
||||||
|
var uuid = Guid.NewGuid();
|
||||||
|
var equipmentId = DraftValidator.DeriveEquipmentId(uuid);
|
||||||
|
using var db = dbFactory.CreateDbContext();
|
||||||
|
db.Namespaces.Add(new Namespace
|
||||||
|
{
|
||||||
|
NamespaceId = "ns-1", ClusterId = "", Kind = NamespaceKind.Equipment, NamespaceUri = "urn:eq",
|
||||||
|
});
|
||||||
|
db.DriverInstances.Add(new DriverInstance
|
||||||
|
{
|
||||||
|
DriverInstanceId = "d-1", ClusterId = "", NamespaceId = "ns-1",
|
||||||
|
Name = "drv", DriverType = driverType, DriverConfig = "{}",
|
||||||
|
});
|
||||||
|
db.Equipment.Add(new Equipment
|
||||||
|
{
|
||||||
|
EquipmentUuid = uuid, EquipmentId = equipmentId, Name = "pump-01",
|
||||||
|
DriverInstanceId = "d-1", UnsLineId = "line-a", MachineCode = "PUMP01",
|
||||||
|
});
|
||||||
|
db.Tags.Add(new Tag
|
||||||
|
{
|
||||||
|
TagId = "tag-1", DriverInstanceId = "d-1", EquipmentId = equipmentId,
|
||||||
|
Name = tagName, DataType = "Int16", AccessLevel = TagAccessLevel.Read, TagConfig = tagConfig,
|
||||||
|
});
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string TypoModbus = "{\"region\":\"HoldingRegisters\",\"address\":1,\"dataType\":\"Intt16\"}";
|
||||||
|
private const string CleanModbus = "{\"region\":\"HoldingRegisters\",\"address\":1,\"dataType\":\"Int16\"}";
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Warn_mode_accepts_and_surfaces_the_warning_in_the_message()
|
||||||
|
{
|
||||||
|
var dbFactory = NewInMemoryDbFactory();
|
||||||
|
Seed(dbFactory, "Modbus", TypoModbus);
|
||||||
|
var coordinator = CreateTestProbe("coord");
|
||||||
|
var actor = Sys.ActorOf(AdminOperationsActor.Props(
|
||||||
|
dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>(), TagConfigValidationMode.Warn));
|
||||||
|
|
||||||
|
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||||
|
|
||||||
|
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
|
||||||
|
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||||
|
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||||
|
reply.Message.ShouldNotBeNull();
|
||||||
|
reply.Message.ShouldContain("Intt16");
|
||||||
|
reply.Message.ShouldContain("tag-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Error_mode_rejects_the_same_draft()
|
||||||
|
{
|
||||||
|
var dbFactory = NewInMemoryDbFactory();
|
||||||
|
Seed(dbFactory, "Modbus", TypoModbus);
|
||||||
|
var coordinator = CreateTestProbe("coord");
|
||||||
|
var actor = Sys.ActorOf(AdminOperationsActor.Props(
|
||||||
|
dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>(), TagConfigValidationMode.Error));
|
||||||
|
|
||||||
|
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||||
|
|
||||||
|
coordinator.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||||
|
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||||
|
reply.Outcome.ShouldBe(StartDeploymentOutcome.Rejected);
|
||||||
|
reply.Message.ShouldNotBeNull();
|
||||||
|
reply.Message.ShouldContain("TagConfigInvalid");
|
||||||
|
reply.Message.ShouldContain("Intt16");
|
||||||
|
|
||||||
|
using var verify = dbFactory.CreateDbContext();
|
||||||
|
verify.Deployments.Count().ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Clean_config_accepts_with_no_tagconfig_noise()
|
||||||
|
{
|
||||||
|
var dbFactory = NewInMemoryDbFactory();
|
||||||
|
Seed(dbFactory, "Modbus", CleanModbus);
|
||||||
|
var coordinator = CreateTestProbe("coord");
|
||||||
|
var actor = Sys.ActorOf(AdminOperationsActor.Props(
|
||||||
|
dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>(), TagConfigValidationMode.Warn));
|
||||||
|
|
||||||
|
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||||
|
|
||||||
|
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
|
||||||
|
var reply = ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3));
|
||||||
|
reply.Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||||
|
(reply.Message is null || !reply.Message.Contains("Intt16")).ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Unmapped_galaxy_tag_is_skipped_even_in_error_mode()
|
||||||
|
{
|
||||||
|
var dbFactory = NewInMemoryDbFactory();
|
||||||
|
// Galaxy tag carrying a FullName (passes the DraftValidator Galaxy rule); the inspector has no
|
||||||
|
// Galaxy mapping, so it is skipped regardless of mode.
|
||||||
|
Seed(dbFactory, "GalaxyMxGateway", "{\"FullName\":\"Obj.Attr\",\"dataType\":\"nonsense\"}");
|
||||||
|
var coordinator = CreateTestProbe("coord");
|
||||||
|
var actor = Sys.ActorOf(AdminOperationsActor.Props(
|
||||||
|
dbFactory, coordinator.Ref, Enumerable.Empty<IDriverProbe>(), TagConfigValidationMode.Error));
|
||||||
|
|
||||||
|
actor.Tell(new StartDeployment("joe", CorrelationId.NewId()));
|
||||||
|
|
||||||
|
coordinator.ExpectMsg<DispatchDeployment>(TimeSpan.FromSeconds(3));
|
||||||
|
ExpectMsg<StartDeploymentResult>(TimeSpan.FromSeconds(3)).Outcome.ShouldBe(StartDeploymentOutcome.Accepted);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user