174 lines
5.7 KiB
C#
174 lines
5.7 KiB
C#
using System.Text.Json;
|
|
|
|
namespace ScadaLink.InboundAPI.Tests;
|
|
|
|
/// <summary>
|
|
/// WP-2: Tests for parameter validation — type checking, required fields, extended type system.
|
|
/// </summary>
|
|
public class ParameterValidatorTests
|
|
{
|
|
[Fact]
|
|
public void NoDefinitions_NoBody_ReturnsValid()
|
|
{
|
|
var result = ParameterValidator.Validate(null, null);
|
|
Assert.True(result.IsValid);
|
|
Assert.Empty(result.Parameters);
|
|
}
|
|
|
|
[Fact]
|
|
public void EmptyDefinitions_ReturnsValid()
|
|
{
|
|
var result = ParameterValidator.Validate(null, "[]");
|
|
Assert.True(result.IsValid);
|
|
}
|
|
|
|
[Fact]
|
|
public void RequiredParameterMissing_ReturnsInvalid()
|
|
{
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "value", Type = "Integer", Required = true }
|
|
});
|
|
|
|
var result = ParameterValidator.Validate(null, definitions);
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains("Missing required parameter", result.ErrorMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void BodyNotObject_ReturnsInvalid()
|
|
{
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "value", Type = "String", Required = true }
|
|
});
|
|
|
|
using var doc = JsonDocument.Parse("\"just a string\"");
|
|
var result = ParameterValidator.Validate(doc.RootElement.Clone(), definitions);
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains("must be a JSON object", result.ErrorMessage);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Boolean", "true", true)]
|
|
[InlineData("Integer", "42", (long)42)]
|
|
[InlineData("Float", "3.14", 3.14)]
|
|
[InlineData("String", "\"hello\"", "hello")]
|
|
public void ValidTypeCoercion_Succeeds(string type, string jsonValue, object expected)
|
|
{
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "val", Type = type, Required = true }
|
|
});
|
|
|
|
using var doc = JsonDocument.Parse($"{{\"val\": {jsonValue}}}");
|
|
var result = ParameterValidator.Validate(doc.RootElement.Clone(), definitions);
|
|
Assert.True(result.IsValid);
|
|
Assert.Equal(expected, result.Parameters["val"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void WrongType_ReturnsInvalid()
|
|
{
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "count", Type = "Integer", Required = true }
|
|
});
|
|
|
|
using var doc = JsonDocument.Parse("{\"count\": \"not a number\"}");
|
|
var result = ParameterValidator.Validate(doc.RootElement.Clone(), definitions);
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains("must be an Integer", result.ErrorMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void ObjectType_Parsed()
|
|
{
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "data", Type = "Object", Required = true }
|
|
});
|
|
|
|
using var doc = JsonDocument.Parse("{\"data\": {\"key\": \"value\"}}");
|
|
var result = ParameterValidator.Validate(doc.RootElement.Clone(), definitions);
|
|
Assert.True(result.IsValid);
|
|
Assert.IsType<Dictionary<string, object?>>(result.Parameters["data"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void ListType_Parsed()
|
|
{
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "items", Type = "List", Required = true }
|
|
});
|
|
|
|
using var doc = JsonDocument.Parse("{\"items\": [1, 2, 3]}");
|
|
var result = ParameterValidator.Validate(doc.RootElement.Clone(), definitions);
|
|
Assert.True(result.IsValid);
|
|
Assert.IsType<List<object?>>(result.Parameters["items"]);
|
|
}
|
|
|
|
[Fact]
|
|
public void OptionalParameter_MissingBody_ReturnsValid()
|
|
{
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "optional", Type = "String", Required = false }
|
|
});
|
|
|
|
var result = ParameterValidator.Validate(null, definitions);
|
|
Assert.True(result.IsValid);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnknownType_ReturnsInvalid()
|
|
{
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "val", Type = "CustomType", Required = true }
|
|
});
|
|
|
|
using var doc = JsonDocument.Parse("{\"val\": \"test\"}");
|
|
var result = ParameterValidator.Validate(doc.RootElement.Clone(), definitions);
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains("Unknown parameter type", result.ErrorMessage);
|
|
}
|
|
|
|
// --- InboundAPI-010: unexpected top-level body fields must be reported so
|
|
// callers get feedback on typo'd parameter names instead of silent ignore. ---
|
|
|
|
[Fact]
|
|
public void UnexpectedBodyField_ReturnsInvalid()
|
|
{
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "value", Type = "Integer", Required = true }
|
|
});
|
|
|
|
// "valeu" is a typo for "value"; the caller must be told, not ignored.
|
|
using var doc = JsonDocument.Parse("{\"value\": 1, \"valeu\": 2}");
|
|
var result = ParameterValidator.Validate(doc.RootElement.Clone(), definitions);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains("valeu", result.ErrorMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void OnlyDefinedFields_StillValid()
|
|
{
|
|
// Regression guard: a body containing exactly the defined parameters
|
|
// must continue to validate.
|
|
var definitions = JsonSerializer.Serialize(new[]
|
|
{
|
|
new { Name = "value", Type = "Integer", Required = true }
|
|
});
|
|
|
|
using var doc = JsonDocument.Parse("{\"value\": 1}");
|
|
var result = ParameterValidator.Validate(doc.RootElement.Clone(), definitions);
|
|
|
|
Assert.True(result.IsValid);
|
|
Assert.Equal((long)1, result.Parameters["value"]);
|
|
}
|
|
}
|