Phase 8: Production readiness — failover tests, security hardening, sandboxing, deployment docs

- WP-1-3: Central/site failover + dual-node recovery tests (17 tests)
- WP-4: Performance testing framework for target scale (7 tests)
- WP-5: Security hardening (LDAPS, JWT key length, no secrets in logs) (11 tests)
- WP-6: Script sandboxing adversarial tests (28 tests, all forbidden APIs)
- WP-7: Recovery drill test scaffolds (5 tests)
- WP-8: Observability validation (structured logs, correlation IDs, metrics) (6 tests)
- WP-9: Message contract compatibility (forward/backward compat) (18 tests)
- WP-10: Deployment packaging (installation guide, production checklist, topology)
- WP-11: Operational runbooks (failover, troubleshooting, maintenance)
92 new tests, all passing. Zero warnings.
This commit is contained in:
Joseph Doherty
2026-03-16 22:12:31 -04:00
parent 3b2320bd35
commit b659978764
68 changed files with 6253 additions and 44 deletions

View File

@@ -0,0 +1,137 @@
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);
}
}