@testable import PureXML import Testing @Suite("Streaming DTD validation") struct StreamingDTDValidationTests { /// Builds a DTD schema from an internal subset by parsing a carrier document. private func schema(_ dtd: String) throws -> PureXML.Validation.DTDSchema { let carrier = "" let parsed = try PureXML.Parsing.Parser().parseWithDocumentType(carrier, limits: .init(allowDoctype: true)) return PureXML.Validation.DTDSchema(parsed.documentType) } @Test("A valid document streams with no errors") func test_valid() throws { let dtd = try schema("") let errors = try PureXML.validate(streaming: "ab", dtd: dtd) #expect(errors.isEmpty) } @Test("A child outside the content model is reported, located at the parent") func test_strayChild() throws { let dtd = try schema("") let errors = try PureXML.validate(streaming: "", dtd: dtd) let failure = try #require(errors.first) #expect(failure.reason.contains("")) #expect(failure.codingPath.map(\.stringValue) == ["list"]) } @Test("A missing required attribute is reported as each element closes") func test_requiredAttribute() throws { let dtd = try schema("") let errors = try PureXML.validate(streaming: "a", dtd: dtd) #expect(errors.contains { $0.reason.contains("id") }) } @Test("A duplicate ID is reported and a forward IDREF resolves at finish") func test_idAndIdref() throws { let dtd = try schema( "", ) // A duplicate ID. let dupes = try PureXML.validate(streaming: "", dtd: dtd) #expect(dupes.contains { $0.reason.contains("duplicate ID 'x'") }) // A forward IDREF (the id appears after the ref) resolves with no error. #expect(try PureXML.validate(streaming: "", dtd: dtd).isEmpty) // A dangling IDREF is reported. let dangling = try PureXML.validate(streaming: "", dtd: dtd) #expect(dangling.contains { $0.reason.contains("IDREF 'z'") }) } @Test("Streaming validation agrees with tree validation on the same document") func test_matchesTreeValidation() throws { let internalSubset = "" let doc = """ ab """ let tree = try PureXML.validateAgainstInternalDTD(doc) let dtd = try schema(internalSubset) let body = "ab" let streamed = try PureXML.validate(streaming: body, dtd: dtd) // Same problems surface (the reasons match; the streamed path always indexes). #expect(streamed.map(\.reason).sorted() == tree.map(\.reason).sorted()) #expect(!streamed.isEmpty) } }