fix(management-service): resolve ManagementService-004,006,007,013 — PipeTo dispatch, JsonDocument disposal, unified serialization, endpoint tests; re-triage MS-009
This commit is contained in:
@@ -668,4 +668,66 @@ public class ManagementActorTests : TestKit, IDisposable
|
||||
var response = ExpectMsg<ManagementUnauthorized>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Serialization (finding ManagementService-007)
|
||||
//
|
||||
// Command results are serialized with System.Text.Json configured with
|
||||
// ReferenceHandler.IgnoreCycles, so an entity graph with a bidirectional
|
||||
// navigation property does not throw. Property names are camelCase, which
|
||||
// the CLI's case-insensitive deserializer accepts.
|
||||
// ========================================================================
|
||||
|
||||
private sealed class CyclicNode
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public CyclicNode? Parent { get; set; }
|
||||
public List<CyclicNode> Children { get; set; } = new();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeResult_WithCyclicGraph_DoesNotThrow()
|
||||
{
|
||||
var parent = new CyclicNode { Name = "Parent" };
|
||||
var child = new CyclicNode { Name = "Child", Parent = parent };
|
||||
parent.Children.Add(child); // parent <-> child cycle
|
||||
|
||||
var json = ManagementActor.SerializeResult(parent);
|
||||
|
||||
Assert.Contains("Parent", json);
|
||||
Assert.Contains("Child", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SerializeResult_UsesCamelCasePropertyNames()
|
||||
{
|
||||
var json = ManagementActor.SerializeResult(new CyclicNode { Name = "X" });
|
||||
|
||||
Assert.Contains("\"name\"", json);
|
||||
Assert.DoesNotContain("\"Name\"", json);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// PipeTo fault mapping (finding ManagementService-004)
|
||||
//
|
||||
// Command processing is piped back via PipeTo; a fault raised inside
|
||||
// DispatchCommand must be mapped to ManagementError by the failure
|
||||
// continuation rather than escaping or being silently dropped.
|
||||
// ========================================================================
|
||||
|
||||
[Fact]
|
||||
public void UnknownCommandType_FaultMappedToManagementError()
|
||||
{
|
||||
// ManagementEnvelope.Command is typed object; an unrecognised payload
|
||||
// makes DispatchCommand throw NotSupportedException. The PipeTo failure
|
||||
// continuation must map it to ManagementError.
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope("not-a-command");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||
Assert.Equal("COMMAND_FAILED", response.ErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using ScadaLink.Commons.Messages.Management;
|
||||
using ScadaLink.ManagementService;
|
||||
|
||||
namespace ScadaLink.ManagementService.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="ManagementEndpoints"/> request-body parsing
|
||||
/// (findings ManagementService-006 / -013).
|
||||
/// </summary>
|
||||
public class ManagementEndpointsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ParseCommand_WithExplicitPayload_DeserializesIntoCommandType()
|
||||
{
|
||||
var json = """{ "command": "CreateSite", "payload": { "name": "Site1", "siteIdentifier": "SITE1", "description": "Desc" } }""";
|
||||
|
||||
var result = ManagementEndpoints.ParseCommand(json);
|
||||
|
||||
Assert.True(result.Success);
|
||||
var command = Assert.IsType<CreateSiteCommand>(result.Command);
|
||||
Assert.Equal("Site1", command.Name);
|
||||
Assert.Equal("SITE1", command.SiteIdentifier);
|
||||
Assert.Equal("Desc", command.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseCommand_WithMissingPayload_DeserializesParameterlessCommand()
|
||||
{
|
||||
// No "payload" field at all -- the fallback must not allocate a throwaway
|
||||
// JsonDocument and must still produce a valid parameterless command.
|
||||
var json = """{ "command": "ListTemplates" }""";
|
||||
|
||||
var result = ManagementEndpoints.ParseCommand(json);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.IsType<ListTemplatesCommand>(result.Command);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseCommand_WithInvalidJson_ReturnsFailure()
|
||||
{
|
||||
var result = ManagementEndpoints.ParseCommand("{ not json");
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("BAD_REQUEST", result.ErrorCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseCommand_WithMissingCommandField_ReturnsFailure()
|
||||
{
|
||||
var result = ManagementEndpoints.ParseCommand("""{ "payload": {} }""");
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("BAD_REQUEST", result.ErrorCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseCommand_WithUnknownCommand_ReturnsFailure()
|
||||
{
|
||||
var result = ManagementEndpoints.ParseCommand("""{ "command": "NoSuchCommand" }""");
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal("BAD_REQUEST", result.ErrorCode);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user