PureXML/Tests/ValidationRuleXSDContentTests.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

160 lines
5.9 KiB
Swift

import Testing
@testable import PureXML
/// Per-rule isolation tests (Validation rules XSD): content and wildcard rules.
@Suite("Validation rules XSD: content and wildcard rules")
struct ValidationRuleXSDContentTests {
// MARK: emptyContentForbidsChildren
private let emptyContentSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="n"><xs:complexType/></xs:element>
</xs:schema>
"""
@Test("emptyContentForbidsChildren rejects children on an empty-content element")
func test_emptyContentForbidsChildren() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.emptyContentForbidsChildren,
schema: emptyContentSchema,
instance: "<n><child/></n>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason == "element must be empty")
}
@Test("emptyContentForbidsChildren accepts a truly empty element")
func test_emptyContentForbidsChildren_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.emptyContentForbidsChildren,
schema: emptyContentSchema,
instance: "<n/>",
rootName: "n",
)
#expect(errors.isEmpty)
}
// MARK: simpleContentForbidsChildren / simpleContentTypeValid
private let simpleContentSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="n">
<xs:complexType><xs:simpleContent><xs:extension base="xs:integer"/></xs:simpleContent></xs:complexType>
</xs:element>
</xs:schema>
"""
@Test("simpleContentForbidsChildren rejects element children under simpleContent")
func test_simpleContentForbidsChildren() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.simpleContentForbidsChildren,
schema: simpleContentSchema,
instance: "<n><child/></n>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason == "element must not have children")
}
@Test("simpleContentForbidsChildren accepts text-only simpleContent")
func test_simpleContentForbidsChildren_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.simpleContentForbidsChildren,
schema: simpleContentSchema,
instance: "<n>5</n>",
rootName: "n",
)
#expect(errors.isEmpty)
}
@Test("simpleContentTypeValid rejects text invalid against the simpleContent type")
func test_simpleContentTypeValid() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.simpleContentTypeValid,
schema: simpleContentSchema,
instance: "<n>x</n>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason.hasPrefix("content:") == true)
}
@Test("simpleContentTypeValid accepts text valid against the simpleContent type")
func test_simpleContentTypeValid_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.simpleContentTypeValid,
schema: simpleContentSchema,
instance: "<n>5</n>",
rootName: "n",
)
#expect(errors.isEmpty)
}
// MARK: textForbidden
private let elementOnlyOptionalSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="n">
<xs:complexType><xs:sequence><xs:element name="a" type="xs:string" minOccurs="0"/></xs:sequence></xs:complexType>
</xs:element>
</xs:schema>
"""
@Test("textForbidden rejects text under element-only content")
func test_textForbidden() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.textForbidden,
schema: elementOnlyOptionalSchema,
instance: "<n>hello</n>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason == "element must not contain text")
}
@Test("textForbidden accepts element-only content with no text")
func test_textForbidden_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.textForbidden,
schema: elementOnlyOptionalSchema,
instance: "<n></n>",
rootName: "n",
)
#expect(errors.isEmpty)
}
// MARK: wildcardElementUndeclared
private let strictWildcardElementSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="n">
<xs:complexType><xs:sequence><xs:any processContents="strict" minOccurs="0"/></xs:sequence></xs:complexType>
</xs:element>
</xs:schema>
"""
@Test("wildcardElementUndeclared rejects a strict wildcard match with no global declaration")
func test_wildcardElementUndeclared() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.wildcardElementUndeclared,
schema: strictWildcardElementSchema,
instance: "<n><undeclared/></n>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason.contains("no declaration for wildcard-matched element") == true)
}
@Test("wildcardElementUndeclared accepts an optional wildcard with no match")
func test_wildcardElementUndeclared_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.wildcardElementUndeclared,
schema: strictWildcardElementSchema,
instance: "<n/>",
rootName: "n",
)
#expect(errors.isEmpty)
}
}