@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: "- a
- b
", 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 = """
- a
- b
"""
let tree = try PureXML.validateAgainstInternalDTD(doc)
let dtd = try schema(internalSubset)
let body = "- a
- b
"
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)
}
}