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) } }