import Testing
@testable import PureXML
/// Structural validity of a schema document against the schema-for-schemas
/// content model (XSD 1.0 Structures): each component's children must be
/// admitted by the model, an `annotation` (where allowed once) must be first,
/// and an identity constraint needs a selector and field. Such a schema is
/// invalid and must be rejected at compile time; the children were previously
/// unchecked and the schema accepted (XSTS invalid-schema ctB/ctG/ctH, etc.).
@Suite("Schema structural validity")
struct SchemaStructureTests {
private func compile(_ body: String) throws {
_ = try PureXML.Schema.Document("""
\(body)
""")
}
private func rejects(_ body: String) -> Bool {
do { try compile(body)
return false
} catch { return true }
}
@Test("well-formed components compile")
func test_valid() throws {
try compile(#"""
doc
"""#)
try compile(#"""
"""#)
}
@Test("a child not admitted by the content model is rejected")
func test_disallowedChild() {
// element is not a direct child of complexType (it must sit in a group).
#expect(rejects(#""#))
// attribute admits only annotation and simpleType, not sequence.
#expect(rejects(#""#))
}
@Test("at most one annotation, and it must be first")
func test_annotationPlacement() {
#expect(rejects(#"""
one
two
"""#))
#expect(rejects(#"""
late
"""#))
}
@Test("schema and redefine may carry several annotations")
func test_multipleAnnotationOnSchema() throws {
try compile(#"""
one
two
"""#)
}
@Test("enumerated attributes accept only their value space")
func test_enumeratedAttributeValues() {
#expect(rejects(#""#))
#expect(rejects(#""#))
#expect(rejects(#""#))
#expect(rejects(#""#))
#expect(rejects(#""#))
}
@Test("minOccurs and maxOccurs must be nonNegativeInteger (maxOccurs also unbounded)")
func test_occursValues() throws {
try compile(#""#)
#expect(rejects(#""#))
#expect(rejects(#""#))
#expect(rejects(#""#))
}
@Test("a particle's minOccurs may not exceed its maxOccurs")
func test_occurrenceOrder() throws {
func wrap(_ particle: String) -> String {
"\(particle)"
}
#expect(rejects(wrap(#""#)))
#expect(rejects(wrap(#""#)))
// Equal bounds, and unbounded, are fine.
try compile(wrap(#""#))
try compile(wrap(#""#))
try compile(wrap(#""#))
}
@Test("valid enumerated and occurrence attribute values compile")
func test_validAttributeValues() throws {
// `use` is valid on a local attribute use, not on a top-level declaration.
try compile(#""#)
try compile(#""#)
try compile(#""#)
}
@Test("a component name must be a valid NCName")
func test_nameNCName() {
#expect(rejects(#""#))
#expect(rejects(#""#))
#expect(rejects(#""#))
#expect(rejects(#""#))
try? compile(#""#)
}
@Test("a QName-valued reference attribute must be a lexical QName")
func test_qnameReferences() throws {
#expect(rejects(#""#))
#expect(rejects(#""#))
try compile(#""#)
try compile(#""#)
}
@Test("a complexType's content shape is exclusive")
func test_complexTypeContentExclusivity() {
// simpleContent/complexContent are mutually exclusive and exclude a model
// group or a direct attribute; only one model group is allowed.
#expect(rejects(#"""
"""#))
#expect(rejects(#"""
"""#))
#expect(rejects(#"""
"""#))
}
@Test("valid complexType content shapes compile")
func test_complexTypeContentValid() throws {
try compile(#"""
"""#)
try compile(#""#)
try compile(#""#)
}
@Test("a named group must contain exactly one model group")
func test_namedGroupContent() throws {
try compile(#""#)
// Two compositors, or none, is invalid.
#expect(rejects(#"""
"""#))
#expect(rejects(#"x"#))
// A group reference (no name) is not a definition and is not flagged here.
try compile(
#""#,
)
}
@Test("an attribute not admitted by a component is rejected")
func test_attributeApplicability() {
#expect(rejects(#""#)) // typo for nillable
#expect(rejects(#""#))
#expect(rejects(#""#)) // sequence has no name
// A foreign-namespace attribute is allowed.
try? compile(#""#)
}
@Test("ref excludes name and type")
func test_refExclusions() {
#expect(rejects(#""#))
#expect(rejects(#""#))
}
@Test("element declarations in one content model must be consistent")
func test_elementDeclsConsistent() throws {
// Same name, different type in one content model is invalid.
#expect(rejects(#"""
"""#))
// Same name, same type is consistent (separated so it stays unambiguous).
try compile(#"""
"""#)
// A same name in a NESTED complex type is a different content model: allowed.
try compile(#"""
"""#)
}
@Test("an identity constraint requires a selector and field")
func test_identityConstraintRequiresParts() {
#expect(rejects(#"""
"""#))
}
@Test("schema elements must follow order: imports/includes/redefines before declarations")
func test_schemaChildrenOrder() throws {
// Redefine after element is rejected
#expect(rejects(#"""
"""#))
// Import after element is rejected
#expect(rejects(#"""
"""#))
// Include after element is rejected
#expect(rejects(#"""
"""#))
// Correct order is compiled successfully
try compile(#"""
"""#)
}
@Test("import constraints: namespace attribute presence and matching targetNamespace")
func test_importConstraints() throws {
// Import without namespace attribute in a schema with NO targetNamespace is rejected
#expect(rejects(#""#))
// Import whose namespace is the same as the targetNamespace is rejected
#expect(throws: Error.self) {
_ = try PureXML.Schema.Document(#"""
"""#)
}
}
}
@Suite("Schema structural order and cardinality validity")
struct SchemaStructureOrderTests {
private func compile(_ body: String) throws {
_ = try PureXML.Schema.Document("""
\(body)
""")
}
private func rejects(_ body: String) -> Bool {
do {
try compile(body)
return false
} catch {
return true
}
}
@Test("element child order and type exclusivity constraints")
func test_elementChildOrderAndExclusivity() throws {
// More than one type definition is rejected
#expect(rejects(#"""
"""#))
// Type definition after identity constraints is rejected
#expect(rejects(#"""
"""#))
// Correct order compiles successfully
try compile(#"""
"""#)
}
@Test("attribute child cardinality constraints")
func test_attributeChildCardinality() {
// More than one simpleType is rejected
#expect(rejects(#"""
"""#))
}
@Test("attributeGroup child order and cardinality constraints")
func test_attributeGroupChildOrderAndCardinality() throws {
// More than one anyAttribute is rejected
#expect(rejects(#"""
"""#))
// Attribute reference after anyAttribute is rejected
#expect(rejects(#"""
"""#))
// Correct order compiles successfully
try compile(#"""
"""#)
}
@Test("group compositor cardinality constraints")
func test_groupCompositorCardinality() {
// More than one compositor is rejected
#expect(rejects(#"""
"""#))
}
/// An xs:include and xs:redefine require a schemaLocation attribute (only
/// xs:import's is optional); one without it is invalid.
@Test("include and redefine require a schemaLocation")
func test_includeRedefineRequireSchemaLocation() {
#expect(rejects(#""#))
#expect(rejects(#""#))
}
/// A local element declaration must identify itself with a name or a ref; an
/// element with neither is invalid, while a named one is valid.
@Test("a local element must have a name or a ref")
func test_localElementNameOrRef() {
#expect(rejects(#""#))
#expect(!rejects(#""#))
}
/// A complexType nested in an element is a local (anonymous) type and must not
/// carry a name; only a top-level complexType is named.
@Test("a local complexType may not have a name")
func test_localComplexTypeMustBeAnonymous() {
#expect(rejects(#""#))
// A named top-level complexType and an anonymous local one are both valid.
try? compile(#""#)
#expect(!rejects(#""#))
}
}