@testable import PureXML
import Testing
@Suite("XSD identity constraints")
struct XSDIdentityTests {
private func validate(_ xsd: String, _ xml: String) throws -> [PureXML.Validation.ValidationError] {
try PureXML.Schema.Document(xsd).validate(xml)
}
private let head = ""
@Test("xs:unique rejects a duplicated field value")
func test_unique() throws {
let xsd = """
\(head)
"""
#expect(try validate(xsd, "
").isEmpty)
#expect(try !validate(xsd, "
").isEmpty)
// unique tolerates a missing field
#expect(try validate(xsd, "
").isEmpty)
}
@Test("xs:key requires every field to be present and distinct")
func test_key() throws {
let xsd = """
\(head)
"""
#expect(try validate(xsd, "
").isEmpty)
#expect(try !validate(xsd, "
").isEmpty)
// a key field may not be absent
#expect(try !validate(xsd, "
").isEmpty)
}
@Test("xs:keyref requires a matching key value")
func test_keyref() throws {
let xsd = """
\(head)
"""
let valid = ""
let dangling = ""
#expect(try validate(xsd, valid).isEmpty)
// The dangling keyref is located at the offending field @product.
let failure = try #require(validate(xsd, dangling).first)
#expect(failure.codingPath.map(\.stringValue) == ["orders", "line", "@product"])
#expect(String(describing: failure).hasSuffix("at path: orders/line/@product"))
}
@Test("Identity-constraint errors are located at the offending field")
func test_identityErrorsCarryPath() throws {
let xsd = """
\(head)
"""
// The error locates the offending field (the duplicate item[2]'s @id),
// not just the element that declares the key.
let errors = try validate(xsd, "
")
let failure = try #require(errors.first)
#expect(failure.codingPath.map(\.stringValue) == ["list", "item", "@id"])
#expect(String(describing: failure).hasSuffix("at path: list/item[2]/@id"))
}
@Test("A nested selector locates the offender at full depth with an intermediate index")
func test_identityErrorDeepPath() throws {
let xsd = """
\(head)
"""
// The duplicate id "1" appears in the second group, so the offender is
// list/group[2]/item: a grandchild, with the index on the intermediate group.
let xml = "
"
let failure = try #require(validate(xsd, xml).first)
#expect(failure.codingPath.map(\.stringValue) == ["list", "group", "item", "@id"])
#expect(String(describing: failure).hasSuffix("at path: list/group[2]/item/@id"))
}
@Test("A keyref resolves against a key declared on an ancestor (cross-scope)")
func test_keyrefCrossScope() throws {
// pk is declared on ; the keyref is declared on the deeper
// , so resolution must find the ancestor's key in scope.
let xsd = """
\(head)
"""
let valid = ""
let dangling = ""
#expect(try validate(xsd, valid).isEmpty)
#expect(try validate(xsd, dangling).contains { $0.reason.contains("no matching key 'pk'") })
}
@Test("A malformed field XPath is a located error, not a silently disabled constraint")
func test_malformedFieldReported() throws {
let xsd = """
\(head)
"""
let errors = try validate(xsd, "
")
#expect(errors.contains { $0.reason.contains("invalid field XPath '@id['") }, "\(errors.map(\.reason))")
}
@Test("A malformed selector XPath is a located error")
func test_malformedSelectorReported() throws {
let xsd = """
\(head)
"""
let errors = try validate(xsd, "
")
#expect(errors.contains { $0.reason.contains("invalid selector XPath 'item['") }, "\(errors.map(\.reason))")
}
@Test("A child-element field locates the error at that child")
func test_childElementField() throws {
let xsd = """
\(head)
"""
let xml = "xx
"
let failure = try #require(validate(xsd, xml).first)
#expect(String(describing: failure).hasSuffix("at path: list/item[2]/code"))
}
}