PureXML/PublicAPITests/PublicSurfaceTests.swift
Mihaela Mihaljevic 380160cc2b refactor(validation): decompose XSD instance validation into composable rules
The two monolithic XSD content/identity rules only satisfied the OpenAPIKit
validation idiom at the wrong granularity: no per-constraint removability,
no isolable failing/near-miss tests. Split them into 32 named rules, each
finding tagged at its origin and cached per XSDContext so the walk still
runs once per document (the #101 decision against duplicating XSD's
recursive type resolution still holds). Wired the streaming path's ID/IDREF
and identity checks through the same framework instead of calling the
validators imperatively, and added the unwrapAndLookup combinator the rule
already named as required.

Also includes the mandated repo-wide swiftformat/swiftlint pass.
2026-07-12 14:12:17 +02:00

67 lines
2.7 KiB
Swift

// Deliberately NOT @testable: these tests compile against the public
// surface exactly as an external package would, so a public facade over
// internal members (the #142 class of gap: a public EventReader whose
// next() was internal) fails CI instead of hiding behind testability.
import PureXML
import Testing
@Suite("Public API surface")
struct PublicSurfaceTests {
@Test("Parse, query, and serialize work through the public surface")
func test_parseQuerySerialize() throws {
let document = try PureXML.parse("<r><a k=\"v\">x</a><a>y</a></r>")
let query = try PureXML.XPath.Query("count(//a)")
#expect(try query.number(over: document) == 2)
#expect(PureXML.serialize(document).contains("<a k=\"v\">x</a>"))
}
@Test("The streaming reader pulls events publicly, bounded memory")
func test_streamingPull() throws {
var reader = PureXML.events("<r><a>x</a><!--c--></r>")
var names: [String] = []
var texts: [String] = []
var comments = 0
while let event = try reader.next() {
switch event {
case let .startElement(name, _): names.append(name.description)
case let .characters(text): texts.append(text)
case .comment: comments += 1
default: break
}
}
#expect(names == ["r", "a"])
#expect(texts == ["x"])
#expect(comments == 1)
}
@Test("The streaming reader accepts a chunked pull source")
func test_streamingFromChunks() throws {
var chunks = ["<r><a>", "hello", "</a></r>"].makeIterator()
var characters: [Character] = []
var reader = PureXML.Parsing.EventReader(pulling: {
if characters.isEmpty, let chunk = chunks.next() {
characters = Array(chunk)
}
return characters.isEmpty ? nil : characters.removeFirst()
})
var events = 0
while try reader.next() != nil {
events += 1
}
#expect(events == 5)
}
@Test("An evaluation budget throws instead of evaluating unbounded")
func test_evaluationBudget() throws {
let tree = try PureXML.parseTree("<r><a/><a/><a/><a/><a/></r>")
let query = try PureXML.XPath.Query("//a")
// Unbounded evaluates; a budget below the result size throws.
#expect(try query.value(at: tree).nodes?.count == 5)
#expect(throws: PureXML.XPath.QueryError.budgetExceeded(3)) {
_ = try query.value(at: tree, budget: .init(maxNodeSetLength: 3))
}
// The libxml2-compatible cap admits ordinary documents.
#expect(try query.value(at: tree, budget: .libxml2Compatible).nodes?.count == 5)
}
}