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

206 lines
9.1 KiB
Swift

import Testing
@testable import PureXML
/// Per-rule isolation tests (Validation rules XSD): Validation rules XSD: attributes.
@Suite("Validation rules XSD: attributes")
struct ValidationRuleXSDAttributeTests {
// MARK: attributeTypeValid / attributeFixedMatches / attributeRequiredPresent / attributeSingleID
private let requiredIntAttributeSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="n"><xs:complexType><xs:attribute name="a" type="xs:integer" use="required"/></xs:complexType></xs:element>
</xs:schema>
"""
@Test("attributeTypeValid rejects an attribute value invalid against its type")
func test_attributeTypeValid() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeTypeValid, schema: requiredIntAttributeSchema, instance: "<n a=\"x\"/>", rootName: "n")
#expect(errors.count == 1)
#expect(errors.first?.reason.contains("attribute 'a'") == true)
}
@Test("attributeTypeValid accepts a valid attribute value")
func test_attributeTypeValid_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeTypeValid, schema: requiredIntAttributeSchema, instance: "<n a=\"0\"/>", rootName: "n")
#expect(errors.isEmpty)
}
@Test("attributeRequiredPresent rejects a missing required attribute")
func test_attributeRequiredPresent() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeRequiredPresent, schema: requiredIntAttributeSchema, instance: "<n/>", rootName: "n")
#expect(errors.count == 1)
#expect(errors.first?.reason.contains("missing required attribute") == true)
}
@Test("attributeRequiredPresent accepts a present required attribute")
func test_attributeRequiredPresent_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.attributeRequiredPresent,
schema: requiredIntAttributeSchema,
instance: "<n a=\"0\"/>",
rootName: "n",
)
#expect(errors.isEmpty)
}
private let fixedAttributeSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="n"><xs:complexType><xs:attribute name="a" type="xs:string" fixed="5"/></xs:complexType></xs:element>
</xs:schema>
"""
@Test("attributeFixedMatches rejects a value that differs from the fixed value")
func test_attributeFixedMatches() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeFixedMatches, schema: fixedAttributeSchema, instance: "<n a=\"6\"/>", rootName: "n")
#expect(errors.count == 1)
#expect(errors.first?.reason.contains("is fixed and must be") == true)
}
@Test("attributeFixedMatches accepts a value equal to the fixed value")
func test_attributeFixedMatches_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeFixedMatches, schema: fixedAttributeSchema, instance: "<n a=\"5\"/>", rootName: "n")
#expect(errors.isEmpty)
}
private let twoIDAttributesSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="n"><xs:complexType><xs:attribute name="a" type="xs:ID"/><xs:attribute name="b" type="xs:ID"/></xs:complexType></xs:element>
</xs:schema>
"""
@Test("attributeSingleID rejects more than one attribute of type ID")
func test_attributeSingleID() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.attributeSingleID,
schema: twoIDAttributesSchema,
instance: "<n a=\"x1\" b=\"x2\"/>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason.contains("at most one attribute of type ID") == true)
}
@Test("attributeSingleID accepts a single attribute of type ID")
func test_attributeSingleID_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeSingleID, schema: twoIDAttributesSchema, instance: "<n a=\"x1\"/>", rootName: "n")
#expect(errors.isEmpty)
}
// MARK: attributeDeclaredOrPermitted
private let emptyContentSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="n"><xs:complexType/></xs:element>
</xs:schema>
"""
@Test("attributeDeclaredOrPermitted rejects an undeclared attribute with no wildcard")
func test_attributeDeclaredOrPermitted() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.attributeDeclaredOrPermitted,
schema: emptyContentSchema,
instance: "<n foo=\"bar\"/>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason.contains("undeclared attribute") == true)
}
@Test("attributeDeclaredOrPermitted accepts no undeclared attributes")
func test_attributeDeclaredOrPermitted_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(PureXML.Validation.XSD.attributeDeclaredOrPermitted, schema: emptyContentSchema, instance: "<n/>", rootName: "n")
#expect(errors.isEmpty)
}
// MARK: wildcardAttributeUndeclared / wildcardAttributeTypeValid / wildcardAttributeFixedMatches
private let strictWildcardAttributeSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="n"><xs:complexType><xs:anyAttribute processContents="strict"/></xs:complexType></xs:element>
</xs:schema>
"""
@Test("wildcardAttributeUndeclared rejects a strict wildcard match with no global declaration")
func test_wildcardAttributeUndeclared() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.wildcardAttributeUndeclared,
schema: strictWildcardAttributeSchema,
instance: "<n foo=\"bar\"/>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason.contains("no declaration for wildcard-matched attribute") == true)
}
@Test("wildcardAttributeUndeclared accepts no wildcard-matched attributes")
func test_wildcardAttributeUndeclared_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.wildcardAttributeUndeclared,
schema: strictWildcardAttributeSchema,
instance: "<n/>",
rootName: "n",
)
#expect(errors.isEmpty)
}
private let laxWildcardIntAttributeSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:attribute name="foo" type="xs:integer"/>
<xs:element name="n"><xs:complexType><xs:anyAttribute processContents="lax"/></xs:complexType></xs:element>
</xs:schema>
"""
@Test("wildcardAttributeTypeValid rejects a wildcard-matched attribute invalid against its type")
func test_wildcardAttributeTypeValid() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.wildcardAttributeTypeValid,
schema: laxWildcardIntAttributeSchema,
instance: "<n foo=\"x\"/>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason.contains("attribute 'foo'") == true)
}
@Test("wildcardAttributeTypeValid accepts a valid wildcard-matched attribute")
func test_wildcardAttributeTypeValid_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.wildcardAttributeTypeValid,
schema: laxWildcardIntAttributeSchema,
instance: "<n foo=\"5\"/>",
rootName: "n",
)
#expect(errors.isEmpty)
}
private let laxWildcardFixedAttributeSchema = """
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:attribute name="foo" type="xs:string" fixed="5"/>
<xs:element name="n"><xs:complexType><xs:anyAttribute processContents="lax"/></xs:complexType></xs:element>
</xs:schema>
"""
@Test("wildcardAttributeFixedMatches rejects a wildcard-matched attribute that differs from its fixed value")
func test_wildcardAttributeFixedMatches() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.wildcardAttributeFixedMatches,
schema: laxWildcardFixedAttributeSchema,
instance: "<n foo=\"6\"/>",
rootName: "n",
)
#expect(errors.count == 1)
#expect(errors.first?.reason.contains("is fixed and must be") == true)
}
@Test("wildcardAttributeFixedMatches accepts a wildcard-matched attribute equal to its fixed value")
func test_wildcardAttributeFixedMatches_succeeds() throws {
let errors = try ValidationRuleTestSupport.xsdErrors(
PureXML.Validation.XSD.wildcardAttributeFixedMatches,
schema: laxWildcardFixedAttributeSchema,
instance: "<n foo=\"5\"/>",
rootName: "n",
)
#expect(errors.isEmpty)
}
}