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(#""#)) } }