feat(mgmt): accept + validate ElementDataType on attribute add/update
This commit is contained in:
@@ -387,6 +387,140 @@ public class ManagementActorTests : TestKit, IDisposable
|
||||
Assert.Contains("Designer", response.Message);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// MV-10: ElementDataType accept + validate on attribute add/update
|
||||
// ========================================================================
|
||||
|
||||
[Fact]
|
||||
public void AddListAttribute_WithStringElementType_PersistsBothColumns()
|
||||
{
|
||||
// A template exists with no attributes; AddAttributeAsync will save the
|
||||
// entity built by the handler. Capture it to assert the persisted shape.
|
||||
var template = new Template("T1") { Id = 1 };
|
||||
_templateRepo.GetTemplateByIdAsync(1, Arg.Any<CancellationToken>()).Returns(template);
|
||||
_templateRepo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(new List<Template> { template });
|
||||
|
||||
TemplateAttribute? saved = null;
|
||||
_templateRepo
|
||||
.When(r => r.AddTemplateAttributeAsync(Arg.Any<TemplateAttribute>(), Arg.Any<CancellationToken>()))
|
||||
.Do(ci => saved = ci.Arg<TemplateAttribute>());
|
||||
_templateRepo.SaveChangesAsync(Arg.Any<CancellationToken>()).Returns(1);
|
||||
_services.AddScoped<TemplateService>();
|
||||
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(
|
||||
new AddTemplateAttributeCommand(1, "Tags", "List", "[\"a\",\"b\"]", null, null, false, "String"),
|
||||
"Designer");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
|
||||
Assert.NotNull(saved);
|
||||
Assert.Equal(Commons.Types.Enums.DataType.List, saved!.DataType);
|
||||
Assert.Equal(Commons.Types.Enums.DataType.String, saved.ElementDataType);
|
||||
Assert.Equal("[\"a\",\"b\"]", saved.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddListAttribute_WithNoElementType_ReturnsManagementError()
|
||||
{
|
||||
_services.AddScoped<TemplateService>();
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(
|
||||
new AddTemplateAttributeCommand(1, "Tags", "List", null, null, null, false, null),
|
||||
"Designer");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("COMMAND_FAILED", response.ErrorCode);
|
||||
Assert.Contains("requires a valid element type", response.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddListAttribute_WithBinaryElementType_ReturnsManagementError()
|
||||
{
|
||||
_services.AddScoped<TemplateService>();
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(
|
||||
new AddTemplateAttributeCommand(1, "Tags", "List", null, null, null, false, "Binary"),
|
||||
"Designer");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("COMMAND_FAILED", response.ErrorCode);
|
||||
Assert.Contains("requires a valid element type", response.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddScalarAttribute_WithElementType_ReturnsManagementError()
|
||||
{
|
||||
_services.AddScoped<TemplateService>();
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(
|
||||
new AddTemplateAttributeCommand(1, "Count", "Int32", null, null, null, false, "String"),
|
||||
"Designer");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("COMMAND_FAILED", response.ErrorCode);
|
||||
Assert.Contains("only valid on List attributes", response.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddListAttribute_WithMalformedDefault_ReturnsManagementError()
|
||||
{
|
||||
_services.AddScoped<TemplateService>();
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(
|
||||
new AddTemplateAttributeCommand(1, "Tags", "List", "[\"a\"", null, null, false, "String"),
|
||||
"Designer");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("COMMAND_FAILED", response.ErrorCode);
|
||||
Assert.Contains("invalid list value", response.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddListAttribute_WithTypeMismatchedDefault_ReturnsManagementError()
|
||||
{
|
||||
_services.AddScoped<TemplateService>();
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(
|
||||
new AddTemplateAttributeCommand(1, "Counts", "List", "[\"x\"]", null, null, false, "Int32"),
|
||||
"Designer");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("COMMAND_FAILED", response.ErrorCode);
|
||||
Assert.Contains("invalid list value", response.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateScalarAttribute_WithElementType_ReturnsManagementError()
|
||||
{
|
||||
// Update path runs the same pre-service validation: a scalar attribute
|
||||
// may not carry an element type, regardless of repository state.
|
||||
_services.AddScoped<TemplateService>();
|
||||
var actor = CreateActor();
|
||||
var envelope = Envelope(
|
||||
new UpdateTemplateAttributeCommand(5, "Count", "Int32", null, null, null, false, "String"),
|
||||
"Designer");
|
||||
|
||||
actor.Tell(envelope);
|
||||
|
||||
var response = ExpectMsg<ManagementError>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal("COMMAND_FAILED", response.ErrorCode);
|
||||
Assert.Contains("only valid on List attributes", response.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UpdateApiKey_WithDesignRole_ReturnsUnauthorized()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user