Repository: marshmallow-code/apispec

Issue:
Resolve components references in components
Currently, we resolve component references in operations but not in components themselves.

It could be nice to do it there as well to allow a user to pass references as strings inside components.

There should be an opportunity for factorization with the code that resolves refs in operations.

Let's finish the refactor in #655 first.

Discussion:

Patch:
diff --git a/docs/special_topics.rst b/docs/special_topics.rst
index 892d4a0..43170a1 100644
--- a/docs/special_topics.rst
+++ b/docs/special_topics.rst
@@ -53,14 +53,29 @@ JSON
 Documenting Top-level Components
 --------------------------------
 
-The ``APISpec`` object contains helpers to add top-level components.
-
-To add a schema (f.k.a. "definition" in OAS v2), use
-`spec.components.schema <apispec.core.Components.schema>`.
-
-Likewise, parameters and responses can be added using
-`spec.components.parameter <apispec.core.Components.parameter>` and
-`spec.components.response <apispec.core.Components.response>`.
+The ``APISpec`` object contains helpers to add top-level components:
+
+.. list-table:: 
+   :header-rows: 1
+
+   * - Component type
+     - Helper method
+     - OpenAPI version
+   * - Schema (f.k.a. "definition" in OAS v2)
+     - `spec.components.schema <apispec.core.Components.schema>`
+     - 2, 3
+   * - Parameter
+     - `spec.components.parameter <apispec.core.Components.parameter>`
+     - 2, 3
+   * - Reponse
+     - `spec.components.response <apispec.core.Components.response>`
+     - 2, 3
+   * - Header
+     - `spec.components.response <apispec.core.Components.header>`
+     - 3
+   * - Example
+     - `spec.components.response <apispec.core.Components.example>`
+     - 3
 
 To add other top-level objects, pass them to the ``APISpec`` as keyword arguments.
 
@@ -134,3 +149,18 @@ to document `Security Scheme Objects <https://github.com/OAI/OpenAPI-Specificati
     pprint(spec.to_dict()["components"]["securitySchemes"], indent=2)
     # { 'api_key': {'in': 'header', 'name': 'X-API-Key', 'type': 'apiKey'},
     #   'jwt': {'bearerFormat': 'JWT', 'scheme': 'bearer', 'type': 'http'}}
+
+Referencing Top-level Components
+--------------------------------
+
+On OpenAPI, top-level component are meant to be referenced using a ``$ref``,
+as in ``{$ref: '#/components/schemas/Pet'}`` (OpenAPI v3) or
+``{$ref: '#/definitions/Pet'}`` (OpenAPI v2).
+
+APISpec automatically resolves references in paths and in components themselves
+when a string is provided while a dict is expected. Passing a fully-resolved
+reference is not supported. In other words, rather than passing
+``{"schema": {$ref: '#/components/schemas/Pet'}}``, the user must pass
+``{"schema": "Pet"}``. APISpec assumes a schema reference named ``"Pet"`` has
+been defined and builds the reference using the components location
+corresponding to the OpenAPI version.
diff --git a/src/apispec/core.py b/src/apispec/core.py
index 54bb7e3..3aa7029 100644
--- a/src/apispec/core.py
+++ b/src/apispec/core.py
@@ -86,6 +86,7 @@ class Components:
                 ret.update(plugin.schema_helper(name, component, **kwargs) or {})
             except PluginMethodNotImplementedError:
                 continue
+        self._resolve_refs_in_schema(ret)
         self.schemas[name] = ret
         return self
 
@@ -110,6 +111,7 @@ class Components:
                 ret.update(plugin.response_helper(component, **kwargs) or {})
             except PluginMethodNotImplementedError:
                 continue
+        self._resolve_refs_in_response(ret)
         self.responses[component_id] = ret
         return self
 
@@ -142,6 +144,7 @@ class Components:
                 ret.update(plugin.parameter_helper(component, **kwargs) or {})
             except PluginMethodNotImplementedError:
                 continue
+        self._resolve_refs_in_parameter_or_header(ret)
         self.parameters[component_id] = ret
         return self
 
@@ -157,6 +160,7 @@ class Components:
             raise DuplicateComponentNameError(
                 f'Another header with name "{component_id}" is already registered.'
             )
+        self._resolve_refs_in_parameter_or_header(component)
         self.headers[component_id] = component
         return self
 
@@ -205,44 +209,66 @@ class Components:
         return build_reference(obj_type, self.openapi_version.major, obj)
 
     def _resolve_schema(self, obj):
-        """Replace schema reference as string with a $ref if needed."""
-        if not isinstance(obj, dict):
-            return
-        if self.openapi_version.major < 3:
-            if "schema" in obj:
-                obj["schema"] = self.get_ref("schema", obj["schema"])
-        else:
-            if "content" in obj:
-                for content in obj["content"].values():
-                    if "schema" in content:
-                        content["schema"] = self.get_ref("schema", content["schema"])
+        """Replace schema reference as string with a $ref if needed
+
+        Also resolve references in the schema
+        """
+        if "schema" in obj:
+            obj["schema"] = self.get_ref("schema", obj["schema"])
+            self._resolve_refs_in_schema(obj["schema"])
 
     def _resolve_examples(self, obj):
         """Replace example reference as string with a $ref"""
         for name, example in obj.get("examples", {}).items():
             obj["examples"][name] = self.get_ref("example", example)
 
-    def _resolve_refs_in_parameter(self, parameter):
-        self._resolve_examples(parameter)
+    def _resolve_refs_in_schema(self, schema):
+        if "properties" in schema:
+            for key in schema["properties"]:
+                schema["properties"][key] = self.get_ref(
+                    "schema", schema["properties"][key]
+                )
+                self._resolve_refs_in_schema(schema["properties"][key])
+        if "items" in schema:
+            schema["items"] = self.get_ref("schema", schema["items"])
+            self._resolve_refs_in_schema(schema["items"])
+        for key in ("allOf", "oneOf", "anyOf"):
+            if key in schema:
+                schema[key] = [self.get_ref("schema", s) for s in schema[key]]
+                for sch in schema[key]:
+                    self._resolve_refs_in_schema(sch)
+        if "not" in schema:
+            schema["not"] = self.get_ref("schema", schema["not"])
+            self._resolve_refs_in_schema(schema["not"])
+
+    def _resolve_refs_in_parameter_or_header(self, parameter_or_header):
+        self._resolve_schema(parameter_or_header)
+        self._resolve_examples(parameter_or_header)
 
     def _resolve_refs_in_request_body(self, request_body):
-        self._resolve_schema(request_body)
+        # requestBody is OpenAPI v3+
         for media_type in request_body["content"].values():
+            self._resolve_schema(media_type)
             self._resolve_examples(media_type)
 
     def _resolve_refs_in_response(self, response):
-        self._resolve_schema(response)
-        for name, header in response.get("headers", {}).items():
-            response["headers"][name] = self.get_ref("header", header)
-        for media_type in response.get("content", {}).values():
-            self._resolve_examples(media_type)
+        if self.openapi_version.major < 3:
+            self._resolve_schema(response)
+        else:
+            for media_type in response.get("content", {}).values():
+                self._resolve_schema(media_type)
+                self._resolve_examples(media_type)
+            for name, header in response.get("headers", {}).items():
+                response["headers"][name] = self.get_ref("header", header)
+                self._resolve_refs_in_parameter_or_header(response["headers"][name])
+            # TODO: Resolve link refs when Components supports links
 
     def _resolve_refs_in_operation(self, operation):
         if "parameters" in operation:
             parameters = []
             for parameter in operation["parameters"]:
                 parameter = self.get_ref("parameter", parameter)
-                self._resolve_refs_in_parameter(parameter)
+                self._resolve_refs_in_parameter_or_header(parameter)
                 parameters.append(parameter)
             operation["parameters"] = parameters
         if "requestBody" in operation:
@@ -260,7 +286,7 @@ class Components:
             parameters = []
             for parameter in path["parameters"]:
                 parameter = self.get_ref("parameter", parameter)
-                self._resolve_refs_in_parameter(parameter)
+                self._resolve_refs_in_parameter_or_header(parameter)
                 parameters.append(parameter)
             path["parameters"] = parameters
         for method in (


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 94bd5cb..66e2ff8 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -1,3 +1,4 @@
+import copy
 from collections import OrderedDict
 from http import HTTPStatus
 
@@ -30,6 +31,69 @@ description = "This is a sample Petstore server.  You can find out more "
 'key "special-key" to test the authorization filters'
 
 
+class RefsSchemaTestMixin:
+    REFS_SCHEMA = {
+        "properties": {
+            "nested": "NestedSchema",
+            "deep_nested": {"properties": {"nested": "NestedSchema"}},
+            "nested_list": {"items": "DeepNestedSchema"},
+            "deep_nested_list": {
+                "items": {"properties": {"nested": "DeepNestedSchema"}}
+            },
+            "allof": {
+                "allOf": [
+                    "AllOfSchema",
+                    {"properties": {"nested": "AllOfSchema"}},
+                ]
+            },
+            "oneof": {
+                "oneOf": [
+                    "OneOfSchema",
+                    {"properties": {"nested": "OneOfSchema"}},
+                ]
+            },
+            "anyof": {
+                "anyOf": [
+                    "AnyOfSchema",
+                    {"properties": {"nested": "AnyOfSchema"}},
+                ]
+            },
+            "not": "NotSchema",
+            "deep_not": {"properties": {"nested": "DeepNotSchema"}},
+        }
+    }
+
+    @staticmethod
+    def assert_schema_refs(spec, schema):
+        props = schema["properties"]
+        assert props["nested"] == build_ref(spec, "schema", "NestedSchema")
+        assert props["deep_nested"]["properties"]["nested"] == build_ref(
+            spec, "schema", "NestedSchema"
+        )
+        assert props["nested_list"]["items"] == build_ref(
+            spec, "schema", "DeepNestedSchema"
+        )
+        assert props["deep_nested_list"]["items"]["properties"]["nested"] == build_ref(
+            spec, "schema", "DeepNestedSchema"
+        )
+        assert props["allof"]["allOf"][0] == build_ref(spec, "schema", "AllOfSchema")
+        assert props["allof"]["allOf"][1]["properties"]["nested"] == build_ref(
+            spec, "schema", "AllOfSchema"
+        )
+        assert props["oneof"]["oneOf"][0] == build_ref(spec, "schema", "OneOfSchema")
+        assert props["oneof"]["oneOf"][1]["properties"]["nested"] == build_ref(
+            spec, "schema", "OneOfSchema"
+        )
+        assert props["anyof"]["anyOf"][0] == build_ref(spec, "schema", "AnyOfSchema")
+        assert props["anyof"]["anyOf"][1]["properties"]["nested"] == build_ref(
+            spec, "schema", "AnyOfSchema"
+        )
+        assert props["not"] == build_ref(spec, "schema", "NotSchema")
+        assert props["deep_not"]["properties"]["nested"] == build_ref(
+            spec, "schema", "DeepNotSchema"
+        )
+
+
 @pytest.fixture(params=("2.0", "3.0.0"))
 def spec(request):
     openapi_version = request.param
@@ -132,7 +196,7 @@ class TestTags:
         assert spec.to_dict()["tags"] == [{"name": "tag1"}, {"name": "tag2"}]
 
 
-class TestComponents:
+class TestComponents(RefsSchemaTestMixin):
 
     properties = {
         "id": {"type": "integer", "format": "int64"},
@@ -321,8 +385,138 @@ class TestComponents:
             "TestSchema": {"properties": {"key": {"type": "string"}}, "type": "object"}
         }
 
+    def test_components_resolve_refs_in_schema(self, spec):
+        spec.components.schema("refs_schema", copy.deepcopy(self.REFS_SCHEMA))
+        self.assert_schema_refs(spec, get_schemas(spec)["refs_schema"])
 
-class TestPath:
+    def test_components_resolve_response_schema(self, spec):
+        schema = {"schema": "PetSchema"}
+        if spec.openapi_version.major >= 3:
+            schema = {"content": {"application/json": schema}}
+        spec.components.response("Response", schema)
+        resp = get_responses(spec)["Response"]
+        if spec.openapi_version.major < 3:
+            schema = resp["schema"]
+        else:
+            schema = resp["content"]["application/json"]["schema"]
+        assert schema == build_ref(spec, "schema", "PetSchema")
+
+    # "headers" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_components_resolve_response_header(self, spec):
+        response = {"headers": {"header_1": "Header_1"}}
+        spec.components.response("Response", response)
+        resp = get_responses(spec)["Response"]
+        header_1 = resp["headers"]["header_1"]
+        assert header_1 == build_ref(spec, "header", "Header_1")
+
+    # "headers" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_components_resolve_response_header_schema(self, spec):
+        response = {"headers": {"header_1": {"name": "Pet", "schema": "PetSchema"}}}
+        spec.components.response("Response", response)
+        resp = get_responses(spec)["Response"]
+        header_1 = resp["headers"]["header_1"]
+        assert header_1["schema"] == build_ref(spec, "schema", "PetSchema")
+
+    # "headers" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_components_resolve_response_header_examples(self, spec):
+        response = {
+            "headers": {
+                "header_1": {"name": "Pet", "examples": {"example_1": "Example_1"}}
+            }
+        }
+        spec.components.response("Response", response)
+        resp = get_responses(spec)["Response"]
+        header_1 = resp["headers"]["header_1"]
+        assert header_1["examples"]["example_1"] == build_ref(
+            spec, "example", "Example_1"
+        )
+
+    # "examples" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_components_resolve_response_examples(self, spec):
+        response = {
+            "content": {"application/json": {"examples": {"example_1": "Example_1"}}}
+        }
+        spec.components.response("Response", response)
+        resp = get_responses(spec)["Response"]
+        example_1 = resp["content"]["application/json"]["examples"]["example_1"]
+        assert example_1 == build_ref(spec, "example", "Example_1")
+
+    def test_components_resolve_refs_in_response_schema(self, spec):
+        schema = copy.deepcopy(self.REFS_SCHEMA)
+        if spec.openapi_version.major >= 3:
+            response = {"content": {"application/json": {"schema": schema}}}
+        else:
+            response = {"schema": schema}
+        spec.components.response("Response", response)
+        resp = get_responses(spec)["Response"]
+        if spec.openapi_version.major < 3:
+            schema = resp["schema"]
+        else:
+            schema = resp["content"]["application/json"]["schema"]
+        self.assert_schema_refs(spec, schema)
+
+    # "headers" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_components_resolve_refs_in_response_header_schema(self, spec):
+        header = {"schema": copy.deepcopy(self.REFS_SCHEMA)}
+        response = {"headers": {"header": header}}
+        spec.components.response("Response", response)
+        resp = get_responses(spec)["Response"]
+        self.assert_schema_refs(spec, resp["headers"]["header"]["schema"])
+
+    # "examples" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_components_resolve_parameter_examples(self, spec):
+        parameter = {
+            "examples": {"example_1": "Example_1"},
+        }
+        spec.components.parameter("param", "path", parameter)
+        param = get_parameters(spec)["param"]
+        example_1 = param["examples"]["example_1"]
+        assert example_1 == build_ref(spec, "example", "Example_1")
+
+    def test_components_resolve_parameter_schemas(self, spec):
+        parameter = {"schema": "PetSchema"}
+        spec.components.parameter("param", "path", parameter)
+        param = get_parameters(spec)["param"]
+        assert param["schema"] == build_ref(spec, "schema", "PetSchema")
+
+    def test_components_resolve_refs_in_parameter_schema(self, spec):
+        parameter = {"schema": copy.deepcopy(self.REFS_SCHEMA)}
+        spec.components.parameter("param", "path", parameter)
+        self.assert_schema_refs(spec, get_parameters(spec)["param"]["schema"])
+
+    # "headers" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_components_resolve_header_schema(self, spec):
+        header = {"name": "Pet", "schema": "PetSchema"}
+        spec.components.header("header", header)
+        header = get_headers(spec)["header"]
+        assert header["schema"] == build_ref(spec, "schema", "PetSchema")
+
+    # "headers" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_components_resolve_header_examples(self, spec):
+        header = {"name": "Pet", "examples": {"example_1": "Example_1"}}
+        spec.components.header("header", header)
+        header = get_headers(spec)["header"]
+        assert header["examples"]["example_1"] == build_ref(
+            spec, "example", "Example_1"
+        )
+
+    # "headers" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_components_resolve_refs_in_header_schema(self, spec):
+        header = {"schema": copy.deepcopy(self.REFS_SCHEMA)}
+        spec.components.header("header", header)
+        self.assert_schema_refs(spec, get_headers(spec)["header"]["schema"])
+
+
+class TestPath(RefsSchemaTestMixin):
     paths = {
         "/pet/{petId}": {
             "get": {
@@ -390,11 +584,11 @@ class TestPath:
             "/path4",
         ]
 
-    def test_paths_is_chainable(self, spec):
+    def test_path_is_chainable(self, spec):
         spec.path(path="/path1").path("/path2")
         assert list(spec.to_dict()["paths"].keys()) == ["/path1", "/path2"]
 
-    def test_methods_maintain_order(self, spec):
+    def test_path_methods_maintain_order(self, spec):
         methods = ["get", "post", "put", "patch", "delete", "head", "options"]
         for method in methods:
             spec.path(path="/path", operations=OrderedDict({method: {}}))
@@ -492,34 +686,20 @@ class TestPath:
         assert p["summary"] == summary
         assert p["description"] == description
 
-    def test_parameter(self, spec):
+    def test_path_resolves_parameter(self, spec):
         route_spec = self.paths["/pet/{petId}"]["get"]
-
         spec.components.parameter("test_parameter", "path", route_spec["parameters"][0])
-
         spec.path(
             path="/pet/{petId}", operations={"get": {"parameters": ["test_parameter"]}}
         )
-
-        metadata = spec.to_dict()
         p = get_paths(spec)["/pet/{petId}"]["get"]
-
         assert p["parameters"][0] == build_ref(spec, "parameter", "test_parameter")
-        if spec.openapi_version.major < 3:
-            assert (
-                route_spec["parameters"][0] == metadata["parameters"]["test_parameter"]
-            )
-        else:
-            assert (
-                route_spec["parameters"][0]
-                == metadata["components"]["parameters"]["test_parameter"]
-            )
 
     @pytest.mark.parametrize(
         "parameters",
         ([{"name": "petId"}], [{"in": "path"}]),  # missing "in"  # missing "name"
     )
-    def test_invalid_parameter(self, spec, parameters):
+    def test_path_invalid_parameter(self, spec, parameters):
         path = "/pet/{petId}"
 
         with pytest.raises(InvalidParameterError):
@@ -594,31 +774,17 @@ class TestPath:
                 ],
             )
 
-    def test_response(self, spec):
+    def test_path_resolves_response(self, spec):
         route_spec = self.paths["/pet/{petId}"]["get"]
-
         spec.components.response("test_response", route_spec["responses"]["200"])
-
         spec.path(
             path="/pet/{petId}",
             operations={"get": {"responses": {"200": "test_response"}}},
         )
-
-        metadata = spec.to_dict()
         p = get_paths(spec)["/pet/{petId}"]["get"]
-
         assert p["responses"]["200"] == build_ref(spec, "response", "test_response")
-        if spec.openapi_version.major < 3:
-            assert (
-                route_spec["responses"]["200"] == metadata["responses"]["test_response"]
-            )
-        else:
-            assert (
-                route_spec["responses"]["200"]
-                == metadata["components"]["responses"]["test_response"]
-            )
 
-    def test_response_with_HTTPStatus_code(self, spec):
+    def test_path_response_with_HTTPStatus_code(self, spec):
         code = HTTPStatus(200)
         spec.path(
             path="/pet/{petId}",
@@ -627,7 +793,7 @@ class TestPath:
 
         assert "200" in get_paths(spec)["/pet/{petId}"]["get"]["responses"]
 
-    def test_response_with_status_code_range(self, spec, recwarn):
+    def test_path_response_with_status_code_range(self, spec, recwarn):
         status_code = "2XX"
 
         spec.path(
@@ -686,6 +852,30 @@ class TestPath:
         header_1 = resp["headers"]["header_1"]
         assert header_1 == build_ref(spec, "header", "Header_1")
 
+    # "headers" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_path_resolve_response_header_schema(self, spec):
+        response = {"headers": {"header_1": {"name": "Pet", "schema": "PetSchema"}}}
+        spec.path("/pet/{petId}", operations={"get": {"responses": {"200": response}}})
+        resp = get_paths(spec)["/pet/{petId}"]["get"]["responses"]["200"]
+        header_1 = resp["headers"]["header_1"]
+        assert header_1["schema"] == build_ref(spec, "schema", "PetSchema")
+
+    # "headers" components section only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_path_resolve_response_header_examples(self, spec):
+        response = {
+            "headers": {
+                "header_1": {"name": "Pet", "examples": {"example_1": "Example_1"}}
+            }
+        }
+        spec.path("/pet/{petId}", operations={"get": {"responses": {"200": response}}})
+        resp = get_paths(spec)["/pet/{petId}"]["get"]["responses"]["200"]
+        header_1 = resp["headers"]["header_1"]
+        assert header_1["examples"]["example_1"] == build_ref(
+            spec, "example", "Example_1"
+        )
+
     # "examples" components section only exists in OAS 3
     @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
     def test_path_resolve_response_examples(self, spec):
@@ -721,6 +911,42 @@ class TestPath:
         example_1 = param["examples"]["example_1"]
         assert example_1 == build_ref(spec, "example", "Example_1")
 
+    def test_path_resolve_parameter_schemas(self, spec):
+        parameter = {"name": "test", "in": "query", "schema": "PetSchema"}
+        spec.path("/pet/{petId}", operations={"get": {"parameters": [parameter]}})
+        param = get_paths(spec)["/pet/{petId}"]["get"]["parameters"][0]
+        assert param["schema"] == build_ref(spec, "schema", "PetSchema")
+
+    def test_path_resolve_refs_in_response_schema(self, spec):
+        if spec.openapi_version.major >= 3:
+            schema = {"content": {"application/json": {"schema": self.REFS_SCHEMA}}}
+        else:
+            schema = {"schema": self.REFS_SCHEMA}
+        spec.path("/pet/{petId}", operations={"get": {"responses": {"200": schema}}})
+        resp = get_paths(spec)["/pet/{petId}"]["get"]["responses"]["200"]
+        if spec.openapi_version.major < 3:
+            schema = resp["schema"]
+        else:
+            schema = resp["content"]["application/json"]["schema"]
+        self.assert_schema_refs(spec, schema)
+
+    def test_path_resolve_refs_in_parameter_schema(self, spec):
+        schema = copy.copy({"schema": self.REFS_SCHEMA})
+        schema["in"] = "query"
+        schema["name"] = "test"
+        spec.path("/pet/{petId}", operations={"get": {"parameters": [schema]}})
+        schema = get_paths(spec)["/pet/{petId}"]["get"]["parameters"][0]["schema"]
+        self.assert_schema_refs(spec, schema)
+
+    # requestBody only exists in OAS 3
+    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
+    def test_path_resolve_refs_in_request_body_schema(self, spec):
+        schema = {"content": {"application/json": {"schema": self.REFS_SCHEMA}}}
+        spec.path("/pet/{petId}", operations={"get": {"responses": {"200": schema}}})
+        resp = get_paths(spec)["/pet/{petId}"]["get"]["responses"]["200"]
+        schema = resp["content"]["application/json"]["schema"]
+        self.assert_schema_refs(spec, schema)
+
 
 class TestPlugins:
     @staticmethod