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.
261 lines
11 KiB
Swift
261 lines
11 KiB
Swift
import Testing
|
|
@testable import PureXML
|
|
|
|
/// Per-rule isolation tests (Validation rules XSD): Validation rules XSD: identity constraints.
|
|
@Suite("Validation rules XSD: identity constraints")
|
|
struct ValidationRuleXSDIdentityTests {
|
|
// MARK: identityConstraintXPathsValid
|
|
|
|
private let uniqueListSchemaTemplate = """
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="list">
|
|
<xs:complexType>
|
|
<xs:sequence><xs:element name="item" maxOccurs="unbounded"><xs:complexType><xs:attribute name="id" type="xs:string"/></xs:complexType></xs:element></xs:sequence>
|
|
</xs:complexType>
|
|
<xs:unique name="byId"><xs:selector xpath="%@"/><xs:field xpath="@id"/></xs:unique>
|
|
</xs:element>
|
|
</xs:schema>
|
|
"""
|
|
|
|
@Test("identityConstraintXPathsValid rejects a malformed selector XPath")
|
|
func test_identityConstraintXPathsValid() throws {
|
|
let schema = String(format: uniqueListSchemaTemplate, "[[[")
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityConstraintXPathsValid,
|
|
schema: schema,
|
|
instance: "<list><item id=\"1\"/></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(errors.count == 1)
|
|
#expect(errors.first?.reason.contains("invalid selector XPath") == true)
|
|
}
|
|
|
|
@Test("identityConstraintXPathsValid accepts a well-formed selector XPath")
|
|
func test_identityConstraintXPathsValid_succeeds() throws {
|
|
let schema = String(format: uniqueListSchemaTemplate, "item")
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityConstraintXPathsValid,
|
|
schema: schema,
|
|
instance: "<list><item id=\"1\"/></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(errors.isEmpty)
|
|
}
|
|
|
|
// MARK: identityKeyFieldPresent
|
|
|
|
private let keyListSchema = """
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="list">
|
|
<xs:complexType>
|
|
<xs:sequence><xs:element name="item" maxOccurs="unbounded"><xs:complexType><xs:attribute name="id" type="xs:string"/></xs:complexType></xs:element></xs:sequence>
|
|
</xs:complexType>
|
|
<xs:key name="byId"><xs:selector xpath="item"/><xs:field xpath="@id"/></xs:key>
|
|
</xs:element>
|
|
</xs:schema>
|
|
"""
|
|
|
|
@Test("identityKeyFieldPresent rejects a key target missing its field")
|
|
func test_identityKeyFieldPresent() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityKeyFieldPresent,
|
|
schema: keyListSchema,
|
|
instance: "<list><item/></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(errors.count == 1)
|
|
#expect(errors.first?.reason.contains("a field is missing") == true)
|
|
}
|
|
|
|
@Test("identityKeyFieldPresent accepts a key target with its field present")
|
|
func test_identityKeyFieldPresent_succeeds() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityKeyFieldPresent,
|
|
schema: keyListSchema,
|
|
instance: "<list><item id=\"1\"/></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(errors.isEmpty)
|
|
}
|
|
|
|
// MARK: identityValueUnique
|
|
|
|
private let uniqueListSchema = """
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="list">
|
|
<xs:complexType>
|
|
<xs:sequence><xs:element name="item" maxOccurs="unbounded"><xs:complexType><xs:attribute name="id" type="xs:string"/></xs:complexType></xs:element></xs:sequence>
|
|
</xs:complexType>
|
|
<xs:unique name="byId"><xs:selector xpath="item"/><xs:field xpath="@id"/></xs:unique>
|
|
</xs:element>
|
|
</xs:schema>
|
|
"""
|
|
|
|
@Test("identityValueUnique reports a duplicate key field")
|
|
func test_identityValueUnique() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityValueUnique,
|
|
schema: uniqueListSchema,
|
|
instance: "<list><item id=\"1\"/><item id=\"1\"/></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(!errors.isEmpty)
|
|
}
|
|
|
|
@Test("identityValueUnique accepts distinct field values")
|
|
func test_identityValueUnique_succeeds() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityValueUnique,
|
|
schema: uniqueListSchema,
|
|
instance: "<list><item id=\"1\"/><item id=\"2\"/></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(errors.isEmpty)
|
|
}
|
|
|
|
// MARK: identityKeyrefResolves
|
|
|
|
private let keyrefSchema = """
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="root">
|
|
<xs:complexType>
|
|
<xs:sequence>
|
|
<xs:element name="items">
|
|
<xs:complexType>
|
|
<xs:sequence>
|
|
<xs:element name="item" maxOccurs="unbounded"><xs:complexType><xs:attribute name="id" type="xs:string"/></xs:complexType></xs:element>
|
|
</xs:sequence>
|
|
</xs:complexType>
|
|
</xs:element>
|
|
<xs:element name="refs">
|
|
<xs:complexType>
|
|
<xs:sequence>
|
|
<xs:element name="ref" maxOccurs="unbounded"><xs:complexType><xs:attribute name="id" type="xs:string"/></xs:complexType></xs:element>
|
|
</xs:sequence>
|
|
</xs:complexType>
|
|
</xs:element>
|
|
</xs:sequence>
|
|
</xs:complexType>
|
|
<xs:key name="itemKey"><xs:selector xpath="items/item"/><xs:field xpath="@id"/></xs:key>
|
|
<xs:keyref name="refKey" refer="itemKey"><xs:selector xpath="refs/ref"/><xs:field xpath="@id"/></xs:keyref>
|
|
</xs:element>
|
|
</xs:schema>
|
|
"""
|
|
|
|
@Test("identityKeyrefResolves rejects a keyref value with no matching key")
|
|
func test_identityKeyrefResolves() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityKeyrefResolves,
|
|
schema: keyrefSchema,
|
|
instance: "<root><items><item id=\"1\"/></items><refs><ref id=\"2\"/></refs></root>",
|
|
rootName: "root",
|
|
)
|
|
#expect(errors.count == 1)
|
|
#expect(errors.first?.reason.contains("no matching key") == true)
|
|
}
|
|
|
|
@Test("identityKeyrefResolves accepts a keyref value that matches a key")
|
|
func test_identityKeyrefResolves_succeeds() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityKeyrefResolves,
|
|
schema: keyrefSchema,
|
|
instance: "<root><items><item id=\"1\"/></items><refs><ref id=\"1\"/></refs></root>",
|
|
rootName: "root",
|
|
)
|
|
#expect(errors.isEmpty)
|
|
}
|
|
|
|
// MARK: identityFieldCardinality
|
|
|
|
private let multiChildFieldSchema = """
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="list">
|
|
<xs:complexType>
|
|
<xs:sequence><xs:element name="item" maxOccurs="unbounded">
|
|
<xs:complexType><xs:sequence><xs:element name="child" type="xs:string" maxOccurs="unbounded"/></xs:sequence></xs:complexType>
|
|
</xs:element></xs:sequence>
|
|
</xs:complexType>
|
|
<xs:unique name="byChild"><xs:selector xpath="item"/><xs:field xpath="child"/></xs:unique>
|
|
</xs:element>
|
|
</xs:schema>
|
|
"""
|
|
|
|
@Test("identityFieldCardinality rejects a field that selects more than one node")
|
|
func test_identityFieldCardinality() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityFieldCardinality,
|
|
schema: multiChildFieldSchema,
|
|
instance: "<list><item><child>a</child><child>b</child></item></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(errors.count == 1)
|
|
#expect(errors.first?.reason.contains("must select at most one node") == true)
|
|
}
|
|
|
|
@Test("identityFieldCardinality accepts a field that selects exactly one node")
|
|
func test_identityFieldCardinality_succeeds() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityFieldCardinality,
|
|
schema: multiChildFieldSchema,
|
|
instance: "<list><item><child>a</child></item></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(errors.isEmpty)
|
|
}
|
|
|
|
// MARK: identityFieldMustBeSimple
|
|
|
|
private let complexChildFieldSchema = """
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="list">
|
|
<xs:complexType>
|
|
<xs:sequence><xs:element name="item" maxOccurs="unbounded">
|
|
<xs:complexType>
|
|
<xs:sequence>
|
|
<xs:element name="child"><xs:complexType><xs:sequence><xs:element name="grand" type="xs:string"/></xs:sequence></xs:complexType></xs:element>
|
|
</xs:sequence>
|
|
</xs:complexType>
|
|
</xs:element></xs:sequence>
|
|
</xs:complexType>
|
|
<xs:unique name="byChild"><xs:selector xpath="item"/><xs:field xpath="child"/></xs:unique>
|
|
</xs:element>
|
|
</xs:schema>
|
|
"""
|
|
|
|
private let simpleChildFieldSchema = """
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
<xs:element name="list">
|
|
<xs:complexType>
|
|
<xs:sequence><xs:element name="item" maxOccurs="unbounded">
|
|
<xs:complexType><xs:sequence><xs:element name="child" type="xs:string"/></xs:sequence></xs:complexType>
|
|
</xs:element></xs:sequence>
|
|
</xs:complexType>
|
|
<xs:unique name="byChild"><xs:selector xpath="item"/><xs:field xpath="child"/></xs:unique>
|
|
</xs:element>
|
|
</xs:schema>
|
|
"""
|
|
|
|
@Test("identityFieldMustBeSimple rejects a field identifying a node with element children")
|
|
func test_identityFieldMustBeSimple() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityFieldMustBeSimple,
|
|
schema: complexChildFieldSchema,
|
|
instance: "<list><item><child><grand>x</grand></child></item></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(errors.count == 1)
|
|
#expect(errors.first?.reason.contains("must identify a node with simple content") == true)
|
|
}
|
|
|
|
@Test("identityFieldMustBeSimple accepts a field identifying a simple-content node")
|
|
func test_identityFieldMustBeSimple_succeeds() throws {
|
|
let errors = try ValidationRuleTestSupport.xsdErrors(
|
|
PureXML.Validation.XSD.identityFieldMustBeSimple,
|
|
schema: simpleChildFieldSchema,
|
|
instance: "<list><item><child>a</child></item></list>",
|
|
rootName: "list",
|
|
)
|
|
#expect(errors.isEmpty)
|
|
}
|
|
}
|