namespace ScadaLink.InboundAPI.Tests; /// /// InboundAPI-014: tests for return-value validation against a method's /// ReturnDefinition. Previously the script's return value was serialized /// verbatim with no checking against the declared return structure. /// public class ReturnValueValidatorTests { // --- No definition → no validation (backward compatible) --- [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] public void NoReturnDefinition_AnythingIsValid(string? returnDefinition) { var result = ReturnValueValidator.Validate("{\"anything\":1}", returnDefinition); Assert.True(result.IsValid); } [Fact] public void NoReturnDefinition_NullResult_IsValid() { var result = ReturnValueValidator.Validate(null, null); Assert.True(result.IsValid); } // --- Happy path: result matches the declared field shape --- [Fact] public void ResultMatchingDefinition_IsValid() { const string def = """[{"name":"siteName","type":"String"},{"name":"totalUnits","type":"Integer"}]"""; const string json = """{"siteName":"Site Alpha","totalUnits":14250}"""; var result = ReturnValueValidator.Validate(json, def); Assert.True(result.IsValid); } [Fact] public void ResultWithListField_ShapeChecked_IsValid() { const string def = """[{"name":"lines","type":"List"}]"""; const string json = """{"lines":[{"lineName":"Line-1","units":8200}]}"""; var result = ReturnValueValidator.Validate(json, def); Assert.True(result.IsValid); } // --- Mismatches must be reported --- [Fact] public void ResultMissingDeclaredField_IsInvalid() { const string def = """[{"name":"siteName","type":"String"},{"name":"totalUnits","type":"Integer"}]"""; const string json = """{"siteName":"Site Alpha"}"""; var result = ReturnValueValidator.Validate(json, def); Assert.False(result.IsValid); Assert.Contains("totalUnits", result.ErrorMessage); } [Fact] public void ResultFieldWrongType_IsInvalid() { const string def = """[{"name":"totalUnits","type":"Integer"}]"""; const string json = """{"totalUnits":"not-a-number"}"""; var result = ReturnValueValidator.Validate(json, def); Assert.False(result.IsValid); Assert.Contains("totalUnits", result.ErrorMessage); } [Fact] public void NullResultWhenStructureRequired_IsInvalid() { const string def = """[{"name":"siteName","type":"String"}]"""; var result = ReturnValueValidator.Validate(null, def); Assert.False(result.IsValid); } [Fact] public void NonObjectResultWhenStructureRequired_IsInvalid() { const string def = """[{"name":"siteName","type":"String"}]"""; var result = ReturnValueValidator.Validate("42", def); Assert.False(result.IsValid); } [Fact] public void ListFieldGivenNonArray_IsInvalid() { const string def = """[{"name":"lines","type":"List"}]"""; const string json = """{"lines":"not-a-list"}"""; var result = ReturnValueValidator.Validate(json, def); Assert.False(result.IsValid); Assert.Contains("lines", result.ErrorMessage); } [Fact] public void MalformedReturnDefinition_IsInvalid() { var result = ReturnValueValidator.Validate("{\"x\":1}", "%%% not json %%%"); Assert.False(result.IsValid); } }