@testable import PureXML import Testing /// 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 { 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("a complexContent derivation's content must be ordered: model group, attributes, anyAttribute") func test_complexContentDerivationOrder() { func derive(_ kind: String, _ body: String) -> String { #""" \#(body) """# } // Two model groups (ctG/ctH "X then Y"). #expect(rejects(derive("restriction", #""#))) #expect(rejects(derive("extension", #""#))) // A model group after an attribute. #expect(rejects(derive("restriction", #""#))) // An attribute after anyAttribute, and two anyAttributes. #expect(rejects(derive("extension", #""#))) #expect(rejects(derive("restriction", #""#))) } @Test("a well-ordered complexContent derivation compiles") func test_complexContentDerivationValid() throws { try compile(#""" """#) // No model group is fine (an extension that only adds attributes). try compile(#""" """#) } @Test("a complexType's shorthand content must be ordered: model group, attributes, anyAttribute") func test_complexTypeShorthandOrder() { // A model group after an attribute, two anyAttributes, an attribute after anyAttribute (ctB). #expect(rejects(#""#)) #expect(rejects(#""#)) #expect(rejects(#""#)) // A well-ordered shorthand complexType compiles. #expect(!rejects(#""" """#)) } }