PureXML/Tests/ValidationConfigurationTests.swift
Mihaela Mihaljevic 380160cc2b refactor(validation): decompose XSD instance validation into composable rules
The two monolithic XSD content/identity rules only satisfied the OpenAPIKit
validation idiom at the wrong granularity: no per-constraint removability,
no isolable failing/near-miss tests. Split them into 32 named rules, each
finding tagged at its origin and cached per XSDContext so the walk still
runs once per document (the #101 decision against duplicating XSD's
recursive type resolution still holds). Wired the streaming path's ID/IDREF
and identity checks through the same framework instead of calling the
validators imperatively, and added the unwrapAndLookup combinator the rule
already named as required.

Also includes the mandated repo-wide swiftformat/swiftlint pass.
2026-07-12 14:12:17 +02:00

161 lines
8.5 KiB
Swift

import Testing
@testable import PureXML
/// Configuration-pin tests: every validator variant lists exactly its active rule
/// descriptions, so adding or rewording a rule fails a test before it is accidental.
@Suite("Validation configuration pins")
struct ValidationConfigurationTests {
@Test("The structural default validator lists its rules")
func test_structuralDefault() {
#expect(PureXML.Validation.Validator<Void>().validationDescriptions == [
"Element attribute names are unique",
])
}
@Test("The blank validator carries no rules")
func test_blank() {
#expect(PureXML.Validation.Validator<Void>.blank.validationDescriptions.isEmpty)
#expect(PureXML.Validation.Validator<PureXML.Validation.DTDSchema>.blank.validationDescriptions.isEmpty)
}
@Test("The lenient DTD validator lists its non-strict rules in order")
func test_dtdLenient() {
let validator = PureXML.Validation.DTD.validator(strict: false)
#expect(validator.nonReferenceValidationDescriptions == [
"Element content matches its DTD content model",
"Required DTD attributes are present",
"#FIXED DTD attributes hold their fixed value",
"Enumerated DTD attributes hold a listed value",
"Tokenized DTD attributes match their declared type",
"NOTATION DTD attributes name a declared notation",
"The DTD declarations satisfy their validity constraints",
"The root element matches the DOCTYPE name",
"Standalone documents do not depend on external attribute declarations",
"Standalone documents have no whitespace in externally-declared element content",
"Parse-time DTD advisories are reviewed",
])
#expect(validator.referenceValidationDescriptions == [
"DTD ID values are unique and IDREFs resolve",
])
}
@Test("The strict DTD validator appends undeclared element and attribute rules to the reference tier")
func test_dtdStrict() {
let validator = PureXML.Validation.DTD.validator(strict: true)
#expect(validator.referenceValidationDescriptions.suffix(2) == [
"Element is declared in the DTD",
"Every attribute is declared in the DTD",
])
}
@Test("The HTML validator splits local and document-scoped rules across tiers")
func test_html() {
let validator = PureXML.Validation.HTML.validator()
#expect(validator.nonReferenceValidationDescriptions == [
"Void HTML elements have no content",
"HTML elements appear inside their required parent",
])
#expect(validator.referenceValidationDescriptions == [
"HTML id attributes are unique",
])
}
@Test("The XSD instance validator splits content and identity rules across tiers")
func test_xsdInstance() {
let validator = PureXML.Validation.XSD.validator()
#expect(validator.nonReferenceValidationDescriptions == [
"An element carries xsi:nil only when it is declared nillable",
"A nilled element has no content",
"A nilled element carries no fixed value constraint",
"A fixed element has no element children",
"A fixed element's content equals its fixed value",
"An empty-content element has no element children",
"A simple-content element has no element children",
"A simple-content element's text is valid against its type",
"An element-only or empty-content element carries no text",
"A strict wildcard-matched element has a global declaration",
"A simple-typed element carries only schema-instance and namespace attributes",
"A simple-typed element has no element children",
"A simple-typed element's value is valid against its type",
"A type reference resolves to a known, non-circular type",
"An element's children conform to its content model",
"A declared attribute's value is valid against its type",
"A declared attribute's value equals its fixed value",
"A required attribute is present",
"An element carries at most one attribute of type ID",
"An attribute is declared or permitted by a wildcard",
"A strict wildcard-matched attribute has a global declaration",
"A wildcard-matched attribute's value is valid against its type",
"A wildcard-matched attribute's value equals its fixed value",
"An xsi:type substitution is validly derived and not blocked",
"An xsi:type override names a known, validly derived type",
"Every xs:ID is unique and every xs:IDREF resolves to one",
])
#expect(validator.referenceValidationDescriptions == [
"An identity constraint's selector and field XPaths compile",
"A key constraint's fields are all present",
"A key or unique constraint's values are distinct",
"A keyref constraint's value matches a key or unique in scope",
"An identity constraint field selects at most one node",
"An identity constraint field identifies a node with simple content",
])
}
@Test("The XSD schema-consistency validator lists named-type rules in tier order")
func test_xsdSchemaConsistency() {
let validator = PureXML.Validation.XSDSchema.validator()
#expect(validator.nonReferenceValidationDescriptions == [
"A type derives from its base only by methods the base permits",
])
#expect(validator.referenceValidationDescriptions == [
"A restriction's content model accepts a subset of its base's",
])
}
@Test("The schema compile validators pin their pre and post rule sets")
func test_schemaCompile() {
#expect(PureXML.Validation.SchemaCompile.preCompileValidator().validationDescriptions == [
"Schema component id attributes are valid NCNames and unique",
"Schema vocabulary elements follow the schema-for-schemas structure",
"Top-level declarations in included or imported documents follow the global-component constraints",
"Global schema component names are unique within their symbol spaces",
"Simple-type final controls are declared consistently",
"Schema content models are deterministic (UPA)",
"Attribute declarations are not in the XSI namespace",
"A resolved schemaLocation reference is a valid schema",
"A redefined type restricts or extends itself",
"A redefined group or attribute group has a well-formed self-reference",
"A redefined attribute group keeps the original's required attributes",
"Type derivation chains contain no cycles",
"Schema type references contain no cycles",
"xs:all group references appear only where permitted",
"Included schemas are chameleon or match the includer targetNamespace",
])
#expect(PureXML.Validation.SchemaCompile.postCompileValidator().validationDescriptions == [
"Attribute uses are unique and declare at most one ID attribute",
"An attribute's type is a simple type",
"ID-typed value constraints are valid",
"Element value constraints are valid against their declared types",
"Complex types extending xs:all groups satisfy XSD placement rules",
"Anonymous complex-type restrictions accept a subset of their base's",
"A content-model derivation is consistent with its base type",
"A redefined model group restricts the group it redefines",
"Attribute restrictions are faithful to their bases",
"Simple types do not derive from complex types",
"Simple-type varieties are declared consistently",
"Notation declarations are valid",
"Simple-type facets are valid for their base type",
"Attribute wildcard unions in type extensions are expressible",
"Every schema reference resolves to a declared component",
"Substitution-group members derive correctly from their head",
])
}
@Test("Every builtin description is unique and pinned")
func test_builtinRegistry() {
let descriptions = PureXML.Validation.BuiltinValidation.allDescriptions
#expect(descriptions.count == Set(descriptions).count)
#expect(descriptions.count == 55)
}
}