import Testing @testable import PureXML /// The streaming validator must run the same `xsi:type` substitution-validity gate /// (cvc-elt.4.3) the tree path runs, so it rejects a blocked, abstract, or /// not-validly-derived override identically (#186). Each case asserts streaming /// agrees with the tree oracle and that the invalid override is rejected, and a /// valid override is accepted on both paths. @Suite("XSD streaming xsi:type substitution gate (#186)") struct SchemaStreamingXsiTypeGateTests { private let schema = """ """ private func agreement(_ instance: String) throws -> [String] { let document = try PureXML.Schema.Document(schema) 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 on:\n\(instance)\n tree: \(tree)\n stream: \(streamed)") return streamed } private func instance(_ element: String, _ type: String, _ body: String) -> String { """ \(body) """ } @Test("a valid derived xsi:type is accepted on both paths") func test_validOverrideAccepted() throws { #expect(try agreement(instance("e", "Derived", "ab")).isEmpty) } @Test("a not-validly-derived xsi:type is rejected on both paths (cvc-elt.4.3.2.1)") func test_notDerivedRejected() throws { #expect(try !agreement(instance("e", "Unrelated", "a")).isEmpty) } @Test("an abstract xsi:type is rejected on both paths") func test_abstractRejected() throws { #expect(try !agreement(instance("e", "Abs", "ab")).isEmpty) } @Test("a blocked xsi:type substitution is rejected on both paths") func test_blockedRejected() throws { #expect(try !agreement(instance("b", "BlockedDerived", "ab")).isEmpty) } @Test("an abstract declared type with no xsi:type is rejected on both paths (cvc-elt.4.2)") func test_abstractDeclaredTypeRequiresXsiType() throws { let noOverride = """ a """ #expect(try !agreement(noOverride).isEmpty) } @Test("a root with no global declaration but a valid xsi:type is accepted on both paths") func test_undeclaredRootWithXsiTypeAccepted() throws { // The root 'undeclared' has no global element declaration; its xsi:type names a // known type, so both paths validate the body against that type (Sun target-NS // tests). Streaming must not report a false "no element declaration". #expect(try agreement(instance("undeclared", "Base", "a")).isEmpty) } @Test("an undeclared root with xsi:type and a wrong body is rejected with the same content error") func test_undeclaredRootWithXsiTypeWrongBody() throws { // Both paths must report the content-model error against the xsi:type, not a // root "no element declaration". let errors = try agreement(instance("undeclared", "Base", "a")) #expect(!errors.isEmpty) #expect(!errors.contains { $0.contains("no element declaration") }, "got: \(errors)") } @Test("a foreign-namespace root sharing a local name with a target-ns element uses its xsi:type, not that element") func test_foreignNamespaceRootDoesNotBindLocalNameElement() throws { // 'e' is a target-ns element typed t:Base. The root here is o:e in urn:other: // it has no global declaration, so its xsi:type='t:Unrelated' (a sibling type // not derived from Base) names the validating type. The bare-local-name root // fallback must NOT bind the target-ns 'e' across namespaces (that would gate // the body against Base and falsely reject the valid Unrelated override). let valid = """ a """ #expect(try agreement(valid).isEmpty) } @Test("a strict-wildcard child typed only by its xsi:type is accepted on both paths") func test_strictWildcardChildTypedByXsiType() throws { // 'item' (urn:o) has no global declaration; a strict ##other wildcard admits it // and its xsi:type names a known type, so both paths validate it against that // type rather than reporting "no declaration for wildcard-matched element". let wildcardSchema = """ """ let xml = """ hello """ let document = try PureXML.Schema.Document(wildcardSchema) let tree = try document.validate(xml).map(\.reason).sorted() let streamed = try document.validate(streaming: xml).map(\.reason).sorted() #expect(tree == streamed, "tree: \(tree)\nstream: \(streamed)") #expect(streamed.isEmpty, "streaming rejected a valid wildcard-xsi:type child: \(streamed)") } }