The validation module already implemented the composable-rule idiom under its own names (Check/Issue/SubjectContext) and a value-type Validator with duplicated traversal machinery per layer. Renamed the core types to the canonical names (Validation/ValidationError/ValidationErrorCollection/ ValidationContext/BuiltinValidation/AnyValidation), converted Validator to a reference-type class with the two-tier default-rule split, and genericized it over both Subject and Document via a new Traversable protocol so the model and tagged layers share one walk engine instead of two copies. Wired the JSON-representability rule set into the same shape and closed the two missing pieces (unwrap combinator, JSON coverage/config-pin tests).
90 lines
3.7 KiB
Swift
90 lines
3.7 KiB
Swift
@testable import PureYAML
|
|
import Testing
|
|
|
|
@Suite("Validation Corpus")
|
|
struct ValidationCorpusTests {
|
|
@Test("Fixture corpus succeeds without diagnostics", arguments: validationCorpusSuccessFixtures)
|
|
func test_fixtureCorpusSucceedsWithoutDiagnostics(testCase: ValidationCorpusSuccessFixture) throws {
|
|
let value = try testCase.source.load()
|
|
let result = testCase.validator.collect(value)
|
|
|
|
#expect(result == PureYAML.Validation.Result())
|
|
#expect(result.isValid)
|
|
#expect(result.errors.isEmpty)
|
|
#expect(result.warnings.isEmpty)
|
|
#expect(try PureYAML.validate(value, using: testCase.validator).isEmpty)
|
|
|
|
for forbiddenIssue in testCase.forbiddenIssues {
|
|
#expect(!result.values.contains(forbiddenIssue))
|
|
}
|
|
}
|
|
|
|
@Test("Fixture corpus reports exact validation issues", arguments: validationCorpusIssueFixtures)
|
|
func test_fixtureCorpusReportsExactValidationIssues(testCase: ValidationCorpusIssueFixture) throws {
|
|
let value = try testCase.source.load()
|
|
let result = testCase.validator.collect(value)
|
|
|
|
#expect(result.values == testCase.expectedIssues)
|
|
#expect(result.values.map(\.description) == testCase.expectedIssues.map(\.description))
|
|
|
|
for forbiddenIssue in testCase.forbiddenIssues {
|
|
#expect(!result.values.contains(forbiddenIssue))
|
|
}
|
|
|
|
expectValidationError(value, using: testCase.validator, strict: testCase.strict) { collection in
|
|
#expect(collection.values == testCase.expectedIssues)
|
|
#expect(collection.description == testCase.expectedIssues.map(\.description).joined(separator: "\n"))
|
|
}
|
|
}
|
|
|
|
@Test("Fixture corpus returns warnings in non-strict mode")
|
|
func test_fixtureCorpusReturnsWarningsInNonStrictMode() throws {
|
|
let fixture = validationCorpusIssueFixtures.first {
|
|
$0.name == "strict warning fixture throws exact warning collection"
|
|
}
|
|
|
|
let testCase = try #require(fixture)
|
|
let value = try testCase.source.load()
|
|
let warnings = try PureYAML.validate(value, using: testCase.validator, strict: false)
|
|
|
|
#expect(warnings == testCase.expectedIssues)
|
|
#expect(warnings.allSatisfy { $0.severity == .warning })
|
|
#expect(!warnings.contains(.init(
|
|
severity: .warning,
|
|
reason: "Forbidden key 'deprecated' is present",
|
|
path: .init([.key("title")]),
|
|
)))
|
|
}
|
|
|
|
@Test("Fixture corpus preserves traversal order")
|
|
func test_fixtureCorpusPreservesTraversalOrder() throws {
|
|
let value = try PureYAML.parse(
|
|
"""
|
|
root:
|
|
mode: legacy
|
|
list:
|
|
- mode: legacy
|
|
""",
|
|
)
|
|
let validator = PureYAML.Validation.Validator<PureYAML.Model.Value, PureYAML.Model.Value>.blank
|
|
.validating(traceRule(label: "first"))
|
|
.validating(forbiddenMappingKeyRule("mode", severity: .warning))
|
|
.validating(legacyModeRule(severity: .error))
|
|
|
|
let result = validator.collect(value)
|
|
|
|
#expect(result.values.map(\.description) == [
|
|
"first at root at root of document",
|
|
"first at $.root at path: $.root",
|
|
"Forbidden key 'mode' is present at path: $.root.mode",
|
|
"first at $.root.mode at path: $.root.mode",
|
|
"Legacy mode is not allowed at path: $.root.mode",
|
|
"first at $.list at path: $.list",
|
|
"first at $.list[0] at path: $.list[0]",
|
|
"Forbidden key 'mode' is present at path: $.list[0].mode",
|
|
"first at $.list[0].mode at path: $.list[0].mode",
|
|
"Legacy mode is not allowed at path: $.list[0].mode",
|
|
])
|
|
}
|
|
}
|