513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
157 lines
6.7 KiB
C#
157 lines
6.7 KiB
C#
using System.Text.Json;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.InboundApi;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.InboundAPI.Tests;
|
|
|
|
/// <summary>
|
|
/// Runtime regression: the InboundAPI RUNTIME validators
|
|
/// (<see cref="ParameterValidator"/> / <see cref="ReturnValueValidator"/>) must
|
|
/// resolve <c>{"$ref":"lib:Name"}</c> library references through the same seam the
|
|
/// deploy-time path uses. Before this fix the runtime validators called the
|
|
/// single-arg <c>InboundApiSchema.Parse(json)</c> with NO resolver, so a schema
|
|
/// containing a <c>$ref</c> threw <see cref="JsonException"/> and every runtime
|
|
/// invocation of the method returned a 400 ("Invalid ... definitions ...") — a
|
|
/// deploy-passes/runtime-breaks defect.
|
|
///
|
|
/// <para>
|
|
/// These tests assert: a VALID ref resolves and validation proceeds against the
|
|
/// referenced shape; a DANGLING ref yields a CLEAR Invalid result naming the
|
|
/// missing reference (NOT an opaque "Invalid ... definitions" from a swallowed
|
|
/// <see cref="JsonException"/>); and schemas WITHOUT a <c>$ref</c> are unaffected
|
|
/// when no resolver is supplied.
|
|
/// </para>
|
|
/// </summary>
|
|
public class SchemaRefRuntimeValidationTests
|
|
{
|
|
private static JsonElement Body(string json)
|
|
{
|
|
using var doc = JsonDocument.Parse(json);
|
|
return doc.RootElement.Clone();
|
|
}
|
|
|
|
// ── ParameterValidator: $ref in parameter definitions ─────────────────────
|
|
|
|
/// <summary>Verifies that the parameter validator resolves a valid library reference and validates against it.</summary>
|
|
[Fact]
|
|
public void ParameterValidator_ValidLibRef_ResolvesAndValidates()
|
|
{
|
|
// The parameter schema is a single {"$ref":"lib:OrderRequest"} that the
|
|
// resolver maps to an object schema with one required integer field.
|
|
const string orderRequest =
|
|
"""{"type":"object","properties":{"qty":{"type":"integer"}},"required":["qty"]}""";
|
|
const string paramDef = """{"$ref":"lib:OrderRequest"}""";
|
|
|
|
var ok = ParameterValidator.Validate(
|
|
Body("""{"qty":5}"""),
|
|
paramDef,
|
|
resolveRef: name => name == "OrderRequest" ? orderRequest : null);
|
|
|
|
Assert.True(ok.IsValid, ok.ErrorMessage);
|
|
Assert.Equal(5L, ok.Parameters["qty"]);
|
|
|
|
// And a body that violates the resolved shape is rejected with a real
|
|
// type error (NOT a configuration/parse error) — proving the ref shape
|
|
// is actually being enforced.
|
|
var bad = ParameterValidator.Validate(
|
|
Body("""{"qty":"not-a-number"}"""),
|
|
paramDef,
|
|
resolveRef: name => name == "OrderRequest" ? orderRequest : null);
|
|
|
|
Assert.False(bad.IsValid);
|
|
Assert.DoesNotContain("definitions in method configuration", bad.ErrorMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that the parameter validator returns a clear invalid result naming a dangling library reference.</summary>
|
|
[Fact]
|
|
public void ParameterValidator_DanglingLibRef_ReturnsClearInvalidNamingTheRef()
|
|
{
|
|
// The resolver cannot find "GoneLib" — at runtime this must surface as a
|
|
// CLEAR Invalid result that names the missing reference, NOT an opaque
|
|
// "Invalid parameter definitions" and NOT a thrown JsonException.
|
|
const string paramDef = """{"$ref":"lib:GoneLib"}""";
|
|
|
|
var result = ParameterValidator.Validate(
|
|
Body("""{"anything":1}"""),
|
|
paramDef,
|
|
resolveRef: _ => null);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.NotNull(result.ErrorMessage);
|
|
Assert.Contains("GoneLib", result.ErrorMessage!, StringComparison.Ordinal);
|
|
}
|
|
|
|
/// <summary>Verifies that the parameter validator behaves unchanged without a resolver when there is no reference.</summary>
|
|
[Fact]
|
|
public void ParameterValidator_NoRef_UnchangedWithoutResolver()
|
|
{
|
|
// A schema with no $ref behaves exactly as before when no resolver is
|
|
// supplied (the default-null resolver path).
|
|
const string def =
|
|
"""{"type":"object","properties":{"count":{"type":"integer"}},"required":["count"]}""";
|
|
|
|
var ok = ParameterValidator.Validate(Body("""{"count":3}"""), def);
|
|
Assert.True(ok.IsValid, ok.ErrorMessage);
|
|
Assert.Equal(3L, ok.Parameters["count"]);
|
|
|
|
var bad = ParameterValidator.Validate(Body("""{"count":"x"}"""), def);
|
|
Assert.False(bad.IsValid);
|
|
}
|
|
|
|
// ── ReturnValueValidator: $ref in return definition ───────────────────────
|
|
|
|
/// <summary>Verifies that the return value validator resolves a valid library reference and validates against it.</summary>
|
|
[Fact]
|
|
public void ReturnValueValidator_ValidLibRef_ResolvesAndValidates()
|
|
{
|
|
const string report =
|
|
"""{"type":"object","properties":{"total":{"type":"integer"}},"required":["total"]}""";
|
|
const string returnDef = """{"$ref":"lib:Report"}""";
|
|
|
|
var ok = ReturnValueValidator.Validate(
|
|
"""{"total":42}""",
|
|
returnDef,
|
|
resolveRef: name => name == "Report" ? report : null);
|
|
|
|
Assert.True(ok.IsValid, ok.ErrorMessage);
|
|
|
|
// A return value violating the resolved shape is rejected as a real
|
|
// validation failure, not a configuration error.
|
|
var bad = ReturnValueValidator.Validate(
|
|
"""{"total":"nope"}""",
|
|
returnDef,
|
|
resolveRef: name => name == "Report" ? report : null);
|
|
|
|
Assert.False(bad.IsValid);
|
|
Assert.DoesNotContain("definition in method configuration", bad.ErrorMessage);
|
|
}
|
|
|
|
/// <summary>Verifies that the return value validator returns a clear invalid result naming a dangling library reference.</summary>
|
|
[Fact]
|
|
public void ReturnValueValidator_DanglingLibRef_ReturnsClearInvalidNamingTheRef()
|
|
{
|
|
const string returnDef = """{"$ref":"lib:GoneReturn"}""";
|
|
|
|
var result = ReturnValueValidator.Validate(
|
|
"""{"total":1}""",
|
|
returnDef,
|
|
resolveRef: _ => null);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains("GoneReturn", result.ErrorMessage, StringComparison.Ordinal);
|
|
}
|
|
|
|
/// <summary>Verifies that the return value validator behaves unchanged without a resolver when there is no reference.</summary>
|
|
[Fact]
|
|
public void ReturnValueValidator_NoRef_UnchangedWithoutResolver()
|
|
{
|
|
const string def =
|
|
"""{"type":"object","properties":{"total":{"type":"integer"}},"required":["total"]}""";
|
|
|
|
var ok = ReturnValueValidator.Validate("""{"total":7}""", def);
|
|
Assert.True(ok.IsValid, ok.ErrorMessage);
|
|
|
|
var bad = ReturnValueValidator.Validate("""{"total":"x"}""", def);
|
|
Assert.False(bad.IsValid);
|
|
}
|
|
}
|