diff --git a/README.md b/README.md index 5aee9db..9a6123d 100644 --- a/README.md +++ b/README.md @@ -1336,7 +1336,7 @@ Implementation notes: ### 9.7 - GTS Type Schema Traits (`x-gts-traits-schema` / `x-gts-traits`) -**OP#13 - Schema Traits Validation**: Validate that `x-gts-traits` values in derived schemas conform to the `x-gts-traits-schema` defined in their base schemas. Verify that, for non-abstract types, all required trait properties in the effective trait-schema are resolved (via explicit value in the chain-merged `x-gts-traits` or via `default` in the effective trait-schema), and that the chain-merged trait values satisfy the effective trait-schema's other constraints (including `const`, which a publisher uses to lock individual trait values across descendants — see §9.7.5). Both `x-gts-traits-schema` and `x-gts-traits` are schema-only keywords and MUST NOT appear in instances. `x-gts-traits-schema` MUST be a valid JSON Schema [subschema](https://json-schema.org/learn/glossary#subschema) (object, `true`, or `false`). Uses the same validation endpoints (`/validate-type-schema`, `/validate-entity`). +**OP#13 - Schema Traits Validation**: Validate that `x-gts-traits` values in derived schemas conform to the `x-gts-traits-schema` defined in their base schemas. Verify that, for non-abstract types, all required trait properties in the effective trait-schema are resolved (via explicit value in the chain-merged `x-gts-traits` or via `default` in the effective trait-schema), and that the chain-merged trait values satisfy the effective trait-schema's other constraints (including `const`, which a publisher uses to lock individual trait values across descendants — see §9.7.5). Both `x-gts-traits-schema` and `x-gts-traits` are GTS Type Schema annotation keywords. `x-gts-traits-schema` MUST be a valid JSON Schema [subschema](https://json-schema.org/learn/glossary#subschema) (object, `true`, or `false`). Uses the same validation endpoints (`/validate-type-schema`, `/validate-entity`). A **schema trait** is a semantic annotation attached to a GTS Type Schema that describes **system behaviour** for processing instances of that type. Traits are not part of the object data model — they do not define instance properties. Instead, they configure cross-cutting concerns such as: @@ -1353,7 +1353,7 @@ Two JSON Schema annotation keywords are used together: | **`x-gts-traits-schema`** | JSON Schema (object \| boolean) | Defines the **shape** of the trait — property names, types, constraints, and `default` values | Base / ancestor schemas | | **`x-gts-traits`** | Plain JSON object | Provides concrete **values** for the trait properties | Derived (leaf) schemas; may also appear alongside `x-gts-traits-schema` in the same schema | -**Schema-only keywords:** Both `x-gts-traits-schema` and `x-gts-traits` are **schema annotation keywords** and MUST only appear in JSON Schema documents (documents with `$schema`). They MUST NOT appear in instance documents. Implementations MUST reject instances that contain these keywords. +**Schema annotation keywords:** Both `x-gts-traits-schema` and `x-gts-traits` have GTS meaning only in JSON Schema documents (documents with `$schema`). In instance documents, fields with these names are ordinary data and have no GTS trait semantics unless the instance's own JSON Schema assigns constraints to them. **Keyword placement:** Both `x-gts-traits-schema` and `x-gts-traits` are type-level keywords and MUST appear at the **top level** of the GTS Type Schema document, adjacent to `$id` and `$schema` — NOT nested inside an `allOf` entry or any other subschema. A misplaced occurrence MUST be rejected (fail fast). This governs only the position of the keyword itself, not the contents of `x-gts-traits-schema` (which is an ordinary JSON Schema subschema and may freely use `$ref`, `allOf`, etc.). The same rule applies to the modifiers `x-gts-final` / `x-gts-abstract` (§9.11). @@ -1557,7 +1557,7 @@ A **schema modifier** is a boolean annotation on a GTS Type Schema that restrict | **`x-gts-final`** | `boolean` | Marks the type as **not inheritable** — no derived schemas may reference it as a base | Leaf schemas; enum-like types with a fixed set of well-known instances | | **`x-gts-abstract`** | `boolean` | Marks the type as **not directly instantiable** — instances must conform to a concrete derived type | Base/ancestor schemas that serve purely as templates | -**Schema-only keywords:** Both `x-gts-final` and `x-gts-abstract` are **schema annotation keywords** and MUST only appear in JSON Schema documents (documents with `$schema`). They MUST NOT appear in instance documents. Implementations MUST reject instances that contain these keywords. +**Schema annotation keywords:** Both `x-gts-final` and `x-gts-abstract` have GTS meaning only in JSON Schema documents (documents with `$schema`). In instance documents, fields with these names are ordinary data and have no GTS modifier semantics unless the instance's own JSON Schema assigns constraints to them. **Allowed values:** The only meaningful value is `true`. If the keyword is absent or set to `false`, it has no effect (the schema behaves normally — both inheritable and instantiable). Implementations MUST reject non-boolean values. @@ -1715,7 +1715,7 @@ Result: ❌ NO MATCH (different major versions) ### 11.0 Relationship to JSON Schema -GTS Type Schemas **extend JSON Schema** with a vendor keyword set (`x-gts-*`) and a set of **registry-enforced semantic rules** (see §3.2 derivation, §9.11 modifiers, OP#12 derivation compatibility, OP#13 trait validation). GTS does **not** impose additional syntactic restrictions on the standard JSON Schema body: any syntactically valid JSON Schema body that carries a valid GTS `$id` is a syntactically valid GTS Type Schema. The constraints GTS does enforce on document structure concern only its own `x-gts-*` keywords — these are type-level annotations that MUST appear at the document top level and are rejected when misplaced, and that MUST NOT appear in instance documents (§9.7.1, §9.11). Implementations MUST treat the GTS keywords described in this specification as layered on top of the underlying JSON Schema dialect's semantics, alongside the standard JSON Schema keywords (`$id`, `$ref`, `allOf`, `const`, …) used here. +GTS Type Schemas **extend JSON Schema** with a vendor keyword set (`x-gts-*`) and a set of **registry-enforced semantic rules** (see §3.2 derivation, §9.11 modifiers, OP#12 derivation compatibility, OP#13 trait validation). GTS does **not** impose additional syntactic restrictions on the standard JSON Schema body: any syntactically valid JSON Schema body that carries a valid GTS `$id` is a syntactically valid GTS Type Schema. The constraints GTS does enforce on document structure concern only its own `x-gts-*` keywords in GTS Type Schemas — these are type-level annotations that MUST appear at the document top level and are rejected when misplaced (§9.7.1, §9.11). Implementations MUST treat the GTS keywords described in this specification as layered on top of the underlying JSON Schema dialect's semantics, alongside the standard JSON Schema keywords (`$id`, `$ref`, `allOf`, `const`, …) used here. **Dialect-agnostic.** GTS does not pin Type Schemas to a single JSON Schema draft. The dialect of any concrete GTS Type Schema is set by its `$schema` URI, and implementations MUST honour that dialect when validating or interpreting the schema body. The reference examples in this specification declare `$schema: http://json-schema.org/draft-07/schema#` because Draft-07 has the broadest tooling support and is the safest baseline for cross-vendor interoperability; however, Type Schemas that declare a later dialect — Draft 2019-09 (`https://json-schema.org/draft/2019-09/schema`) or Draft 2020-12 (`https://json-schema.org/draft/2020-12/schema`) — are equally valid GTS Type Schemas. Authors who wish to use post-Draft-07 keywords (`$defs`, `prefixItems`, `unevaluatedProperties`, `unevaluatedItems`, `$dynamicRef`/`$dynamicAnchor`, `dependentRequired`, `dependentSchemas`, …) MAY do so, provided the dialect declared in `$schema` admits those keywords and the GTS-specific rules (derivation compatibility per OP#12, trait validation per OP#13, modifiers per §9.11) are satisfied. diff --git a/tests/test_op12_type_derivation_validation.py b/tests/test_op12_type_derivation_validation.py index fdd1449..e0dcc75 100644 --- a/tests/test_op12_type_derivation_validation.py +++ b/tests/test_op12_type_derivation_validation.py @@ -494,6 +494,66 @@ def test_start(self): ] +class TestCaseTestOp12_NestedClosedDescendantOrphansAncestorProperty( + HttpRunner +): + """OP#12 - Descendant nested AP:false must not orphan ancestor properties. + + Base declares routing.source. The derived overlay keeps the top-level + routing object but closes that nested object while declaring only + routing.target. Under allOf, any routing.source value is rejected by the + derived branch, so the derived schema makes an ancestor property unusable + and must fail type-schema validation. + """ + + config = Config( + "OP#12 - nested closed descendant orphans ancestor property" + ).base_url(get_gts_base_url()) + + def test_start(self): + super().test_start() + + teststeps = [ + _register( + "gts://gts.x.test12.nested.orphan.v1~", + { + "type": "object", + "properties": { + "routing": { + "type": "object", + "properties": { + "source": {"type": "string"}, + }, + }, + }, + }, + "register base with nested routing.source property", + ), + _register_derived( + "gts://gts.x.test12.nested.orphan.v1~x.test12._.child.v1~", + "gts://gts.x.test12.nested.orphan.v1~", + { + "type": "object", + "properties": { + "routing": { + "type": "object", + "additionalProperties": False, + "properties": { + "target": {"type": "string"}, + }, + }, + }, + }, + "register derived closing routing without restating source", + ), + _validate_type_schema( + "gts.x.test12.nested.orphan.v1~x.test12._.child.v1~", + False, + "validate should fail - closed nested branch orphans routing.source", + ), + ] + + class TestCaseTestOp12TypeDerivationValidation_InvalidDerivedSchema( HttpRunner ): diff --git a/tests/test_op13_schema_traits_validation.py b/tests/test_op13_schema_traits_validation.py index c2d85bd..1e3e847 100644 --- a/tests/test_op13_schema_traits_validation.py +++ b/tests/test_op13_schema_traits_validation.py @@ -3,6 +3,7 @@ register as _register, register_derived as _register_derived, register_abstract as _register_abstract, + register_instance as _register_instance, validate_entity as _validate_entity, validate_type_schema as _validate_type_schema, ) @@ -1402,6 +1403,65 @@ def test_start(self): ] +class TestCaseOp13_TraitsInvalid_AbstractAPBlocksSchemaExtensionNoValues(HttpRunner): + """OP#13 - Traits: abstract descendant cannot extend a closed trait schema. + + The failure must be based on descendant trait-schema compatibility, not on + validating provided x-gts-traits values: this abstract descendant provides + no trait values and skips completeness, but its new topicRef property is + still rejected by the ancestor's additionalProperties:false branch. + """ + + config = Config( + "OP#13 - Abstract traits schema extension blocked by AP false" + ).base_url(get_gts_base_url()) + + def test_start(self): + super().test_start() + + teststeps = [ + _register_abstract( + "gts://gts.x.test13.apabs.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "additionalProperties": False, + "properties": { + "retention": {"type": "string"}, + }, + }, + "required": ["id"], + "properties": {"id": {"type": "string"}}, + }, + "register abstract base with closed traits-schema", + ), + _register_derived( + "gts://gts.x.test13.apabs.event.v1~x.test13._.ap_child.v1~", + "gts://gts.x.test13.apabs.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "topicRef": { + "type": "string", + "x-gts-ref": "gts.x.core.events.topic.v1~", + }, + }, + }, + }, + "register abstract child extending closed trait schema", + top_level={"x-gts-abstract": True}, + ), + _validate_type_schema( + "gts.x.test13.apabs.event.v1~x.test13._.ap_child.v1~", + False, + "validate should fail - abstract child trait schema is incompatible", + ), + ] + + class TestCaseOp13_TraitsInvalid_DerivedHasTraitsButNoTraitSchema(HttpRunner): """OP#13 - Traits: derived provides x-gts-traits. @@ -1801,114 +1861,6 @@ def test_start(self): # TestCaseOp13_TraitsValueViolatesIntegerSchema below. -class TestCaseOp13_TraitsInvalid_TraitsInInstance(HttpRunner): - """OP#13 - Traits: x-gts-traits in an instance document. - - Trait keywords are schema-only. Instance with x-gts-traits - must fail entity validation. - """ - config = Config( - "OP#13 - Traits In Instance" - ).base_url(get_gts_base_url()) - - def test_start(self): - super().test_start() - - teststeps = [ - _register( - "gts://gts.x.test13.tinst.event.v1~", - { - "type": "object", - "x-gts-traits-schema": { - "type": "object", - "properties": { - "retention": { - "type": "string", - "default": "P30D", - }, - }, - }, - "required": ["id"], - "properties": { - "id": {"type": "string"}, - }, - }, - "register base schema with traits", - ), - _register_derived( - ( - "gts://gts.x.test13.tinst.event.v1~" - "x.test13._.tinst_leaf.v1~" - ), - "gts://gts.x.test13.tinst.event.v1~", - { - "type": "object", - "x-gts-traits": { - "retention": "P90D", - }, - }, - "register derived with traits", - ), - _validate_type_schema( - ( - "gts.x.test13.tinst.event.v1~" - "x.test13._.tinst_leaf.v1~" - ), - True, - "validate derived schema - ok", - ), - _validate_entity( - ( - "gts.x.test13.tinst.event.v1~" - "x.test13._.tinst_leaf.v1~" - ), - False, - "validate entity should fail - traits in instance", - ), - ] - - -class TestCaseOp13_TraitsInvalid_TraitsSchemaInInstance(HttpRunner): - """OP#13 - Traits: x-gts-traits-schema in an instance document. - - Trait keywords are schema-only. Instance with - x-gts-traits-schema must fail entity validation. - """ - config = Config( - "OP#13 - Traits Schema In Instance" - ).base_url(get_gts_base_url()) - - def test_start(self): - super().test_start() - - teststeps = [ - _register( - "gts://gts.x.test13.tsinst.event.v1~", - { - "type": "object", - "x-gts-traits-schema": { - "type": "object", - "properties": { - "retention": { - "type": "string", - "default": "P30D", - }, - }, - }, - "required": ["id"], - "properties": { - "id": {"type": "string"}, - }, - }, - "register base schema with traits-schema", - ), - _validate_entity( - "gts.x.test13.tsinst.event.v1~", - False, - "validate entity should fail - traits-schema in instance", - ), - ] - # --------------------------------------------------------------------------- # ADR-0002: x-gts-traits-schema as a JSON Schema subschema @@ -2271,6 +2223,326 @@ def test_start(self): ] +class TestCaseOp13_TraitsSchema_AbstractIncompatibleNoValues_Fails(HttpRunner): + """ADR-0002: abstract descendant trait-schema must still narrow ancestor. + + The abstract child provides no x-gts-traits values, so ordinary value + validation has nothing concrete to reject. Validation must still fail + because retention changes from string to integer in the descendant + x-gts-traits-schema, which is not a narrowing of the ancestor trait. + """ + + config = Config( + "OP#13 ADR-0002: abstract incompatible trait-schema without values" + ).base_url(get_gts_base_url()) + + def test_start(self): + super().test_start() + + teststeps = [ + _register_abstract( + "gts://gts.x.test13.abstincomp.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "retention": {"type": "string"}, + }, + }, + "required": ["id"], + "properties": {"id": {"type": "string"}}, + }, + "register abstract base with retention string trait", + ), + _register_derived( + ( + "gts://gts.x.test13.abstincomp.event.v1~" + "x.test13._.child.v1~" + ), + "gts://gts.x.test13.abstincomp.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "retention": {"type": "integer"}, + }, + }, + }, + "register abstract child changing retention trait type", + top_level={"x-gts-abstract": True}, + ), + _validate_type_schema( + "gts.x.test13.abstincomp.event.v1~x.test13._.child.v1~", + False, + "validate should fail - abstract child trait schema changes type", + ), + ] + + +class TestCaseOp13_TraitsSchema_AbstractClosedDescendantOrphansAncestor_Fails( + HttpRunner +): + """ADR-0002: a closed descendant trait-schema must not orphan an ancestor trait. + + The effective trait-schema composes ancestor and descendant contributions as + sibling allOf branches, and additionalProperties is scoped to its own branch. + The abstract child sets additionalProperties:false but does not restate the + ancestor's retention trait, so retention becomes unusable (any value for it + is rejected by the child branch). The child provides no values, so this is + caught by descendant trait-schema compatibility, not value validation. + """ + + config = Config( + "OP#13 ADR-0002: closed descendant orphans ancestor trait" + ).base_url(get_gts_base_url()) + + def test_start(self): + super().test_start() + + teststeps = [ + _register_abstract( + "gts://gts.x.test13.abstorphan.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "retention": {"type": "string"}, + }, + }, + "required": ["id"], + "properties": {"id": {"type": "string"}}, + }, + "register abstract base with retention string trait", + ), + _register_derived( + ( + "gts://gts.x.test13.abstorphan.event.v1~" + "x.test13._.child.v1~" + ), + "gts://gts.x.test13.abstorphan.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "additionalProperties": False, + "properties": { + "topicRef": {"type": "string"}, + }, + }, + }, + "register abstract child closing surface without restating retention", + top_level={"x-gts-abstract": True}, + ), + _validate_type_schema( + "gts.x.test13.abstorphan.event.v1~x.test13._.child.v1~", + False, + "validate should fail - closed child orphans ancestor retention trait", + ), + ] + + +class TestCaseOp13_TraitsSchema_AbstractNestedClosedDescendantOrphansAncestor_Fails( + HttpRunner +): + """ADR-0002: closed nested descendant trait-schema must not orphan ancestors. + + The child keeps the top-level routing trait but closes the nested routing + object without restating the ancestor's routing.source field. Under allOf, + any routing.source value is rejected by the child branch, so validation + must fail even though the abstract child provides no trait values. + """ + + config = Config( + "OP#13 ADR-0002: closed nested descendant orphans ancestor trait" + ).base_url(get_gts_base_url()) + + def test_start(self): + super().test_start() + + teststeps = [ + _register_abstract( + "gts://gts.x.test13.abstnestedorphan.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "routing": { + "type": "object", + "properties": { + "source": {"type": "string"}, + }, + }, + }, + }, + "required": ["id"], + "properties": {"id": {"type": "string"}}, + }, + "register abstract base with nested routing.source trait", + ), + _register_derived( + ( + "gts://gts.x.test13.abstnestedorphan.event.v1~" + "x.test13._.child.v1~" + ), + "gts://gts.x.test13.abstnestedorphan.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "routing": { + "type": "object", + "additionalProperties": False, + "properties": { + "target": {"type": "string"}, + }, + }, + }, + }, + }, + "register abstract child closing routing without source", + top_level={"x-gts-abstract": True}, + ), + _validate_type_schema( + "gts.x.test13.abstnestedorphan.event.v1~x.test13._.child.v1~", + False, + "validate should fail - closed nested child orphans routing.source", + ), + ] + + +class TestCaseOp13_TraitsSchema_AbstractNestedClosedDescendantRestatesAncestor_Ok( + HttpRunner +): + """ADR-0002: closed nested descendant may restate ancestor nested traits.""" + + config = Config( + "OP#13 ADR-0002: closed nested descendant restates ancestor trait" + ).base_url(get_gts_base_url()) + + def test_start(self): + super().test_start() + + teststeps = [ + _register_abstract( + "gts://gts.x.test13.abstnestedrestate.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "routing": { + "type": "object", + "properties": { + "source": {"type": "string"}, + }, + }, + }, + }, + "required": ["id"], + "properties": {"id": {"type": "string"}}, + }, + "register abstract base with nested routing.source trait", + ), + _register_derived( + ( + "gts://gts.x.test13.abstnestedrestate.event.v1~" + "x.test13._.child.v1~" + ), + "gts://gts.x.test13.abstnestedrestate.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "routing": { + "type": "object", + "additionalProperties": False, + "properties": { + "source": {"type": "string"}, + "target": {"type": "string"}, + }, + }, + }, + }, + }, + "register abstract child closing routing and restating source", + top_level={"x-gts-abstract": True}, + ), + _validate_type_schema( + "gts.x.test13.abstnestedrestate.event.v1~x.test13._.child.v1~", + True, + "validate should pass - closed nested child restates routing.source", + ), + ] + + +class TestCaseOp13_TraitsSchema_AbstractValidNarrowingNoValues_Ok(HttpRunner): + """ADR-0002: a valid narrowing descendant trait-schema must pass. + + Positive guard against over-rejection: the abstract child narrows the + ancestor priority trait (open string -> enum subset) and adds an optional + trait under an open ancestor, providing no values. Descendant trait-schema + compatibility must accept this. + """ + + config = Config( + "OP#13 ADR-0002: abstract valid narrowing trait-schema without values" + ).base_url(get_gts_base_url()) + + def test_start(self): + super().test_start() + + teststeps = [ + _register_abstract( + "gts://gts.x.test13.abstnarrow.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "priority": {"type": "string"}, + }, + }, + "required": ["id"], + "properties": {"id": {"type": "string"}}, + }, + "register abstract base with open priority trait", + ), + _register_derived( + ( + "gts://gts.x.test13.abstnarrow.event.v1~" + "x.test13._.child.v1~" + ), + "gts://gts.x.test13.abstnarrow.event.v1~", + { + "type": "object", + "x-gts-traits-schema": { + "type": "object", + "properties": { + "priority": { + "type": "string", + "enum": ["low", "high"], + }, + "note": {"type": "string"}, + }, + }, + }, + "register abstract child narrowing priority and adding optional note", + top_level={"x-gts-abstract": True}, + ), + _validate_type_schema( + "gts.x.test13.abstnarrow.event.v1~x.test13._.child.v1~", + True, + "validate should pass - child narrows priority and adds optional trait", + ), + ] + + class TestCaseOp13_TraitsSchema_RedundantAncestorRefAllowed(HttpRunner): """ADR-0002 §"Patterns within Option 2A": redundant ancestor-ref is allowed. @@ -4211,4 +4483,3 @@ def test_start(self): "validate leaf - retention re-added after mid deleted it", ), ] - diff --git a/tests/test_refimpl_x_gts_final_abstract.py b/tests/test_refimpl_x_gts_final_abstract.py index 51d65f0..fb1b686 100644 --- a/tests/test_refimpl_x_gts_final_abstract.py +++ b/tests/test_refimpl_x_gts_final_abstract.py @@ -4,7 +4,7 @@ - x-gts-final: type cannot be inherited - x-gts-abstract: type cannot be directly instantiated -These tests cover edge cases, boolean validation, schema-only enforcement, +These tests cover edge cases, boolean validation, type-schema keyword placement, and interactions with x-gts-traits (OP#13). """ @@ -273,49 +273,6 @@ def test_start(self): ] -class TestCaseFinal_InInstanceRejected(HttpRunner): - """x-gts-final: Schema-only keyword in an instance MUST be rejected. - - An instance body containing x-gts-final should fail entity validation. - """ - - config = Config("final: in instance rejected").base_url(get_gts_base_url()) - - def test_start(self): - super().test_start() - - teststeps = [ - _register( - "gts://gts.x.testfa.finalininst.base.v1~", - { - "type": "object", - "required": ["id"], - "properties": { - "id": {"type": "string"}, - }, - }, - "register base schema", - ), - _register_instance( - { - "id": "gts.x.testfa.finalininst.base.v1~x.testfa._.item.v1", - "x-gts-final": True, - }, - "register instance with x-gts-final in body", - ), - _validate_entity( - "gts.x.testfa.finalininst.base.v1~x.testfa._.item.v1", - False, - "validate-entity instance with schema keyword should fail", - expected_entity_type="instance", - ), - ] - - -# --------------------------------------------------------------------------- -# x-gts-abstract tests -# --------------------------------------------------------------------------- - class TestCaseAbstract_RejectDirectInstance(HttpRunner): """x-gts-abstract: Direct instance of abstract type MUST fail validation.""" @@ -555,39 +512,6 @@ def test_start(self): ] -class TestCaseAbstract_InInstanceRejected(HttpRunner): - """x-gts-abstract: Schema-only keyword in an instance MUST be rejected.""" - - config = Config("abstract: in instance rejected").base_url(get_gts_base_url()) - - def test_start(self): - super().test_start() - - teststeps = [ - _register( - "gts://gts.x.testfa.absininst.base.v1~", - { - "type": "object", - "required": ["id"], - "properties": {"id": {"type": "string"}}, - }, - "register base schema", - ), - _register_instance( - { - "id": "gts.x.testfa.absininst.base.v1~x.testfa._.item.v1", - "x-gts-abstract": True, - }, - "register instance with x-gts-abstract", - ), - _validate_entity( - "gts.x.testfa.absininst.base.v1~x.testfa._.item.v1", - False, - "validate-entity instance with schema keyword should fail", - expected_entity_type="instance", - ), - ] - class TestCaseAbstract_CombinedAnonInstanceRejected(HttpRunner): """x-gts-abstract: Combined anonymous instance of abstract type MUST fail."""