import Testing
@testable import PureXML
/// An empty element with a `default`/`fixed` value constraint takes that value as
/// its content, and that value (not the empty string) must satisfy the element's
/// simple type (cvc-elt.5.1.2 / cvc-elt.5.2.2.2). The streaming validator must apply
/// the constraint the same way the tree path does (#200), so an empty element with a
/// valid default is not falsely rejected. Each case asserts streaming agrees with
/// the tree-path oracle.
@Suite("XSD streaming default/fixed value constraints (#200)")
struct SchemaStreamingValueConstraintTests {
private func schema(_ constraint: String) -> String {
"""
"""
}
private func agreement(_ schemaSource: String, _ instance: String) throws -> [String] {
let document = try PureXML.Schema.Document(schemaSource)
let tree = try document.validate(instance).map(\.reason).sorted()
let streamed = try document.validate(streaming: instance).map(\.reason).sorted()
#expect(tree == streamed, "streaming disagreed with tree:\n tree: \(tree)\n stream: \(streamed)")
return streamed
}
@Test("an empty element with a valid default is accepted on both paths")
func test_emptyTakesDefault() throws {
let instance = ""
#expect(try agreement(schema("default=\"5\""), instance).isEmpty)
}
@Test("an empty element with a valid fixed value is accepted on both paths")
func test_emptyTakesFixed() throws {
let instance = ""
#expect(try agreement(schema("fixed=\"5\""), instance).isEmpty)
}
@Test("a present non-empty value is still validated against the type on both paths")
func test_presentValueStillValidated() throws {
let instance = "notanint"
#expect(try !agreement(schema("default=\"5\""), instance).isEmpty)
}
@Test("same-local-name elements with different defaults use the matched particle, not the flat map")
func test_collidingLocalNameConstraints() throws {
// Two complex types both declare a local element 'v' with a different default;
// B is declared after A, so the by-local-name elementConstraints map keeps B's
// (type-invalid for A). Streaming must use A's matched-particle default "5" for
// an empty under root (type A), not the colliding "hello".
let collisionSchema = """
"""
let instance = ""
#expect(try agreement(collisionSchema, instance).isEmpty)
}
@Test("a present fixed value is compared in the type's value space, not raw string")
func test_fixedValueSpaceComparison() throws {
// "05" equals fixed "5" in the xs:int value space; both paths accept.
let instance = "05"
#expect(try agreement(schema("fixed=\"5\""), instance).isEmpty)
}
@Test("a present fixed value that differs in value space is rejected on both paths")
func test_fixedValueMismatchRejected() throws {
let instance = "6"
#expect(try !agreement(schema("fixed=\"5\""), instance).isEmpty)
}
@Test("a present fixed value under a same-local-name collision uses the matched particle")
func test_fixedCollisionPresentContent() throws {
let collisionSchema = """
"""
// 5 under root (type A) matches A's fixed "5", not B's colliding "hello".
let instance = "5"
#expect(try agreement(collisionSchema, instance).isEmpty)
}
}