1import json
2
3import pytest
4
5from marshmallow.fields import Field, DateTime, Dict, String, Nested, List, TimeDelta
6from marshmallow import Schema
7
8from apispec import APISpec
9from apispec.ext.marshmallow import MarshmallowPlugin
10from apispec.ext.marshmallow import common
11from apispec.exceptions import APISpecError
12from .schemas import (
13    PetSchema,
14    SampleSchema,
15    AnalysisSchema,
16    RunSchema,
17    SelfReferencingSchema,
18    OrderedSchema,
19    PatternedObjectSchema,
20    DefaultValuesSchema,
21    AnalysisWithListSchema,
22)
23
24from .utils import (
25    get_schemas,
26    get_parameters,
27    get_responses,
28    get_headers,
29    get_paths,
30    build_ref,
31)
32
33
34class TestDefinitionHelper:
35    @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
36    def test_can_use_schema_as_definition(self, spec, schema):
37        spec.components.schema("Pet", schema=schema)
38        definitions = get_schemas(spec)
39        props = definitions["Pet"]["properties"]
40
41        assert props["id"]["type"] == "integer"
42        assert props["name"]["type"] == "string"
43
44    def test_schema_helper_without_schema(self, spec):
45        spec.components.schema("Pet", {"properties": {"key": {"type": "integer"}}})
46        definitions = get_schemas(spec)
47        assert definitions["Pet"]["properties"] == {"key": {"type": "integer"}}
48
49    @pytest.mark.parametrize("schema", [AnalysisSchema, AnalysisSchema()])
50    def test_resolve_schema_dict_auto_reference(self, schema):
51        def resolver(schema):
52            schema_cls = common.resolve_schema_cls(schema)
53            return schema_cls.__name__
54
55        spec = APISpec(
56            title="Test auto-reference",
57            version="0.1",
58            openapi_version="2.0",
59            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
60        )
61        with pytest.raises(KeyError):
62            get_schemas(spec)
63
64        spec.components.schema("analysis", schema=schema)
65        spec.path(
66            "/test",
67            operations={
68                "get": {
69                    "responses": {
70                        "200": {"schema": build_ref(spec, "schema", "analysis")}
71                    }
72                }
73            },
74        )
75        definitions = get_schemas(spec)
76        assert 3 == len(definitions)
77
78        assert "analysis" in definitions
79        assert "SampleSchema" in definitions
80        assert "RunSchema" in definitions
81
82    @pytest.mark.parametrize(
83        "schema", [AnalysisWithListSchema, AnalysisWithListSchema()]
84    )
85    def test_resolve_schema_dict_auto_reference_in_list(self, schema):
86        def resolver(schema):
87            schema_cls = common.resolve_schema_cls(schema)
88            return schema_cls.__name__
89
90        spec = APISpec(
91            title="Test auto-reference",
92            version="0.1",
93            openapi_version="2.0",
94            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
95        )
96        with pytest.raises(KeyError):
97            get_schemas(spec)
98
99        spec.components.schema("analysis", schema=schema)
100        spec.path(
101            "/test",
102            operations={
103                "get": {
104                    "responses": {
105                        "200": {"schema": build_ref(spec, "schema", "analysis")}
106                    }
107                }
108            },
109        )
110        definitions = get_schemas(spec)
111        assert 3 == len(definitions)
112
113        assert "analysis" in definitions
114        assert "SampleSchema" in definitions
115        assert "RunSchema" in definitions
116
117    @pytest.mark.parametrize("schema", [AnalysisSchema, AnalysisSchema()])
118    def test_resolve_schema_dict_auto_reference_return_none(self, schema):
119        def resolver(schema):
120            return None
121
122        spec = APISpec(
123            title="Test auto-reference",
124            version="0.1",
125            openapi_version="2.0",
126            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
127        )
128        with pytest.raises(KeyError):
129            get_schemas(spec)
130
131        with pytest.raises(
132            APISpecError, match="Name resolver returned None for schema"
133        ):
134            spec.components.schema("analysis", schema=schema)
135
136    @pytest.mark.parametrize("schema", [AnalysisSchema, AnalysisSchema()])
137    def test_warning_when_schema_added_twice(self, spec, schema):
138        spec.components.schema("Analysis", schema=schema)
139        with pytest.warns(UserWarning, match="has already been added to the spec"):
140            spec.components.schema("DuplicateAnalysis", schema=schema)
141
142    def test_schema_instances_with_different_modifiers_added(self, spec):
143        class MultiModifierSchema(Schema):
144            pet_unmodified = Nested(PetSchema)
145            pet_exclude = Nested(PetSchema, exclude=("name",))
146
147        spec.components.schema("Pet", schema=PetSchema())
148        spec.components.schema("Pet_Exclude", schema=PetSchema(exclude=("name",)))
149
150        spec.components.schema("MultiModifierSchema", schema=MultiModifierSchema)
151
152        definitions = get_schemas(spec)
153        pet_unmodified_ref = definitions["MultiModifierSchema"]["properties"][
154            "pet_unmodified"
155        ]
156        assert pet_unmodified_ref == build_ref(spec, "schema", "Pet")
157
158        pet_exclude = definitions["MultiModifierSchema"]["properties"]["pet_exclude"]
159        assert pet_exclude == build_ref(spec, "schema", "Pet_Exclude")
160
161    def test_schema_instance_with_different_modifers_custom_resolver(self):
162        class MultiModifierSchema(Schema):
163            pet_unmodified = Nested(PetSchema)
164            pet_exclude = Nested(PetSchema(partial=True))
165
166        def resolver(schema):
167            schema_instance = common.resolve_schema_instance(schema)
168            prefix = "Partial-" if schema_instance.partial else ""
169            schema_cls = common.resolve_schema_cls(schema)
170            name = prefix + schema_cls.__name__
171            if name.endswith("Schema"):
172                return name[:-6] or name
173            return name
174
175        spec = APISpec(
176            title="Test Custom Resolver for Partial",
177            version="0.1",
178            openapi_version="2.0",
179            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
180        )
181
182        with pytest.warns(None) as record:
183            spec.components.schema("NameClashSchema", schema=MultiModifierSchema)
184
185        assert len(record) == 0
186
187    def test_schema_with_clashing_names(self, spec):
188        class Pet(PetSchema):
189            another_field = String()
190
191        class NameClashSchema(Schema):
192            pet_1 = Nested(PetSchema)
193            pet_2 = Nested(Pet)
194
195        with pytest.warns(
196            UserWarning, match="Multiple schemas resolved to the name Pet"
197        ):
198            spec.components.schema("NameClashSchema", schema=NameClashSchema)
199
200        definitions = get_schemas(spec)
201
202        assert "Pet" in definitions
203        assert "Pet1" in definitions
204
205    def test_resolve_nested_schema_many_true_resolver_return_none(self):
206        def resolver(schema):
207            return None
208
209        class PetFamilySchema(Schema):
210            pets_1 = Nested(PetSchema, many=True)
211            pets_2 = List(Nested(PetSchema))
212
213        spec = APISpec(
214            title="Test auto-reference",
215            version="0.1",
216            openapi_version="2.0",
217            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
218        )
219
220        spec.components.schema("PetFamily", schema=PetFamilySchema)
221        props = get_schemas(spec)["PetFamily"]["properties"]
222        pets_1 = props["pets_1"]
223        pets_2 = props["pets_2"]
224        assert pets_1["type"] == pets_2["type"] == "array"
225
226
227class TestComponentParameterHelper:
228    @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
229    def test_can_use_schema_in_parameter(self, spec, schema):
230        param = {"schema": schema}
231        spec.components.parameter("Pet", "body", param)
232        parameter = get_parameters(spec)["Pet"]
233        assert parameter["in"] == "body"
234        reference = parameter["schema"]
235        assert reference == build_ref(spec, "schema", "Pet")
236
237        resolved_schema = spec.components.schemas["Pet"]
238        assert resolved_schema["properties"]["name"]["type"] == "string"
239        assert resolved_schema["properties"]["password"]["type"] == "string"
240        assert resolved_schema["properties"]["id"]["type"] == "integer"
241
242    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
243    @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
244    def test_can_use_schema_in_parameter_with_content(self, spec, schema):
245        param = {"content": {"application/json": {"schema": schema}}}
246        spec.components.parameter("Pet", "body", param)
247        parameter = get_parameters(spec)["Pet"]
248        assert parameter["in"] == "body"
249        reference = parameter["content"]["application/json"]["schema"]
250        assert reference == build_ref(spec, "schema", "Pet")
251
252        resolved_schema = spec.components.schemas["Pet"]
253        assert resolved_schema["properties"]["name"]["type"] == "string"
254        assert resolved_schema["properties"]["password"]["type"] == "string"
255        assert resolved_schema["properties"]["id"]["type"] == "integer"
256
257
258class TestComponentResponseHelper:
259    @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
260    def test_can_use_schema_in_response(self, spec, schema):
261        if spec.openapi_version.major < 3:
262            resp = {"schema": schema}
263        else:
264            resp = {"content": {"application/json": {"schema": schema}}}
265        spec.components.response("GetPetOk", resp)
266        response = get_responses(spec)["GetPetOk"]
267        if spec.openapi_version.major < 3:
268            reference = response["schema"]
269        else:
270            reference = response["content"]["application/json"]["schema"]
271        assert reference == build_ref(spec, "schema", "Pet")
272
273        resolved_schema = spec.components.schemas["Pet"]
274        assert resolved_schema["properties"]["id"]["type"] == "integer"
275        assert resolved_schema["properties"]["name"]["type"] == "string"
276        assert resolved_schema["properties"]["password"]["type"] == "string"
277
278    @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
279    def test_can_use_schema_in_response_header(self, spec, schema):
280        resp = {"headers": {"PetHeader": {"schema": schema}}}
281        spec.components.response("GetPetOk", resp)
282        response = get_responses(spec)["GetPetOk"]
283        reference = response["headers"]["PetHeader"]["schema"]
284        assert reference == build_ref(spec, "schema", "Pet")
285
286        resolved_schema = spec.components.schemas["Pet"]
287        assert resolved_schema["properties"]["id"]["type"] == "integer"
288        assert resolved_schema["properties"]["name"]["type"] == "string"
289        assert resolved_schema["properties"]["password"]["type"] == "string"
290
291    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
292    def test_content_without_schema(self, spec):
293        resp = {"content": {"application/json": {"example": {"name": "Example"}}}}
294        spec.components.response("GetPetOk", resp)
295        response = get_responses(spec)["GetPetOk"]
296        assert response == resp
297
298
299class TestComponentHeaderHelper:
300    @pytest.mark.parametrize("spec", ("3.0.0",), indirect=True)
301    @pytest.mark.parametrize("schema", [PetSchema, PetSchema()])
302    def test_can_use_schema_in_header(self, spec, schema):
303        param = {"schema": schema}
304        spec.components.header("Pet", param)
305        header = get_headers(spec)["Pet"]
306        reference = header["schema"]
307        assert reference == build_ref(spec, "schema", "Pet")
308
309        resolved_schema = spec.components.schemas["Pet"]
310        assert resolved_schema["properties"]["name"]["type"] == "string"
311        assert resolved_schema["properties"]["password"]["type"] == "string"
312        assert resolved_schema["properties"]["id"]["type"] == "integer"
313
314
315class TestCustomField:
316    def test_can_use_custom_field_decorator(self, spec_fixture):
317        @spec_fixture.marshmallow_plugin.map_to_openapi_type(DateTime)
318        class CustomNameA(Field):
319            pass
320
321        @spec_fixture.marshmallow_plugin.map_to_openapi_type("integer", "int32")
322        class CustomNameB(Field):
323            pass
324
325        with pytest.raises(TypeError):
326
327            @spec_fixture.marshmallow_plugin.map_to_openapi_type("integer")
328            class BadCustomField(Field):
329                pass
330
331        class CustomPetASchema(PetSchema):
332            name = CustomNameA()
333
334        class CustomPetBSchema(PetSchema):
335            name = CustomNameB()
336
337        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
338        spec_fixture.spec.components.schema("CustomPetA", schema=CustomPetASchema)
339        spec_fixture.spec.components.schema("CustomPetB", schema=CustomPetBSchema)
340
341        props_0 = get_schemas(spec_fixture.spec)["Pet"]["properties"]
342        props_a = get_schemas(spec_fixture.spec)["CustomPetA"]["properties"]
343        props_b = get_schemas(spec_fixture.spec)["CustomPetB"]["properties"]
344
345        assert props_0["name"]["type"] == "string"
346        assert "format" not in props_0["name"]
347
348        assert props_a["name"]["type"] == "string"
349        assert props_a["name"]["format"] == "date-time"
350
351        assert props_b["name"]["type"] == "integer"
352        assert props_b["name"]["format"] == "int32"
353
354
355def get_nested_schema(schema, field_name):
356    try:
357        return schema._declared_fields[field_name]._schema
358    except AttributeError:
359        return schema._declared_fields[field_name]._Nested__schema
360
361
362class TestOperationHelper:
363    @pytest.fixture
364    def make_pet_callback_spec(self, spec_fixture):
365        def _make_pet_spec(operations):
366            spec_fixture.spec.path(
367                path="/pet",
368                operations={
369                    "post": {"callbacks": {"petEvent": {"petCallbackUrl": operations}}}
370                },
371            )
372            return spec_fixture
373
374        return _make_pet_spec
375
376    @pytest.mark.parametrize(
377        "pet_schema",
378        (PetSchema, PetSchema(), PetSchema(many=True), "tests.schemas.PetSchema"),
379    )
380    @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
381    def test_schema_v2(self, spec_fixture, pet_schema):
382        spec_fixture.spec.path(
383            path="/pet",
384            operations={
385                "get": {
386                    "responses": {
387                        200: {
388                            "schema": pet_schema,
389                            "description": "successful operation",
390                            "headers": {"PetHeader": {"schema": pet_schema}},
391                        }
392                    }
393                }
394            },
395        )
396        get = get_paths(spec_fixture.spec)["/pet"]["get"]
397        if isinstance(pet_schema, Schema) and pet_schema.many is True:
398            assert get["responses"]["200"]["schema"]["type"] == "array"
399            schema_reference = get["responses"]["200"]["schema"]["items"]
400            assert (
401                get["responses"]["200"]["headers"]["PetHeader"]["schema"]["type"]
402                == "array"
403            )
404            header_reference = get["responses"]["200"]["headers"]["PetHeader"][
405                "schema"
406            ]["items"]
407        else:
408            schema_reference = get["responses"]["200"]["schema"]
409            header_reference = get["responses"]["200"]["headers"]["PetHeader"]["schema"]
410        assert schema_reference == build_ref(spec_fixture.spec, "schema", "Pet")
411        assert header_reference == build_ref(spec_fixture.spec, "schema", "Pet")
412        assert len(spec_fixture.spec.components.schemas) == 1
413        resolved_schema = spec_fixture.spec.components.schemas["Pet"]
414        assert resolved_schema == spec_fixture.openapi.schema2jsonschema(PetSchema)
415        assert get["responses"]["200"]["description"] == "successful operation"
416
417    @pytest.mark.parametrize(
418        "pet_schema",
419        (PetSchema, PetSchema(), PetSchema(many=True), "tests.schemas.PetSchema"),
420    )
421    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
422    def test_schema_v3(self, spec_fixture, pet_schema):
423        spec_fixture.spec.path(
424            path="/pet",
425            operations={
426                "get": {
427                    "responses": {
428                        200: {
429                            "content": {"application/json": {"schema": pet_schema}},
430                            "description": "successful operation",
431                            "headers": {"PetHeader": {"schema": pet_schema}},
432                        }
433                    }
434                }
435            },
436        )
437        get = get_paths(spec_fixture.spec)["/pet"]["get"]
438        if isinstance(pet_schema, Schema) and pet_schema.many is True:
439            assert (
440                get["responses"]["200"]["content"]["application/json"]["schema"]["type"]
441                == "array"
442            )
443            schema_reference = get["responses"]["200"]["content"]["application/json"][
444                "schema"
445            ]["items"]
446            assert (
447                get["responses"]["200"]["headers"]["PetHeader"]["schema"]["type"]
448                == "array"
449            )
450            header_reference = get["responses"]["200"]["headers"]["PetHeader"][
451                "schema"
452            ]["items"]
453        else:
454            schema_reference = get["responses"]["200"]["content"]["application/json"][
455                "schema"
456            ]
457            header_reference = get["responses"]["200"]["headers"]["PetHeader"]["schema"]
458
459        assert schema_reference == build_ref(spec_fixture.spec, "schema", "Pet")
460        assert header_reference == build_ref(spec_fixture.spec, "schema", "Pet")
461        assert len(spec_fixture.spec.components.schemas) == 1
462        resolved_schema = spec_fixture.spec.components.schemas["Pet"]
463        assert resolved_schema == spec_fixture.openapi.schema2jsonschema(PetSchema)
464        assert get["responses"]["200"]["description"] == "successful operation"
465
466    @pytest.mark.parametrize(
467        "pet_schema",
468        (PetSchema, PetSchema(), PetSchema(many=True), "tests.schemas.PetSchema"),
469    )
470    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
471    def test_callback_schema_v3(self, make_pet_callback_spec, pet_schema):
472        spec_fixture = make_pet_callback_spec(
473            {
474                "get": {
475                    "responses": {
476                        "200": {
477                            "content": {"application/json": {"schema": pet_schema}},
478                            "description": "successful operation",
479                            "headers": {"PetHeader": {"schema": pet_schema}},
480                        }
481                    }
482                }
483            }
484        )
485        p = get_paths(spec_fixture.spec)["/pet"]
486        c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
487        get = c["get"]
488        if isinstance(pet_schema, Schema) and pet_schema.many is True:
489            assert (
490                get["responses"]["200"]["content"]["application/json"]["schema"]["type"]
491                == "array"
492            )
493            schema_reference = get["responses"]["200"]["content"]["application/json"][
494                "schema"
495            ]["items"]
496            assert (
497                get["responses"]["200"]["headers"]["PetHeader"]["schema"]["type"]
498                == "array"
499            )
500            header_reference = get["responses"]["200"]["headers"]["PetHeader"][
501                "schema"
502            ]["items"]
503        else:
504            schema_reference = get["responses"]["200"]["content"]["application/json"][
505                "schema"
506            ]
507            header_reference = get["responses"]["200"]["headers"]["PetHeader"]["schema"]
508
509        assert schema_reference == build_ref(spec_fixture.spec, "schema", "Pet")
510        assert header_reference == build_ref(spec_fixture.spec, "schema", "Pet")
511        assert len(spec_fixture.spec.components.schemas) == 1
512        resolved_schema = spec_fixture.spec.components.schemas["Pet"]
513        assert resolved_schema == spec_fixture.openapi.schema2jsonschema(PetSchema)
514        assert get["responses"]["200"]["description"] == "successful operation"
515
516    @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
517    def test_schema_expand_parameters_v2(self, spec_fixture):
518        spec_fixture.spec.path(
519            path="/pet",
520            operations={
521                "get": {"parameters": [{"in": "query", "schema": PetSchema}]},
522                "post": {
523                    "parameters": [
524                        {
525                            "in": "body",
526                            "description": "a pet schema",
527                            "required": True,
528                            "name": "pet",
529                            "schema": PetSchema,
530                        }
531                    ]
532                },
533            },
534        )
535        p = get_paths(spec_fixture.spec)["/pet"]
536        get = p["get"]
537        assert get["parameters"] == spec_fixture.openapi.schema2parameters(
538            PetSchema(), location="query"
539        )
540        post = p["post"]
541        assert post["parameters"] == spec_fixture.openapi.schema2parameters(
542            PetSchema,
543            location="body",
544            required=True,
545            name="pet",
546            description="a pet schema",
547        )
548
549    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
550    def test_schema_expand_parameters_v3(self, spec_fixture):
551        spec_fixture.spec.path(
552            path="/pet",
553            operations={
554                "get": {"parameters": [{"in": "query", "schema": PetSchema}]},
555                "post": {
556                    "requestBody": {
557                        "description": "a pet schema",
558                        "required": True,
559                        "content": {"application/json": {"schema": PetSchema}},
560                    }
561                },
562            },
563        )
564        p = get_paths(spec_fixture.spec)["/pet"]
565        get = p["get"]
566        assert get["parameters"] == spec_fixture.openapi.schema2parameters(
567            PetSchema(), location="query"
568        )
569        for parameter in get["parameters"]:
570            description = parameter.get("description", False)
571            assert description
572            name = parameter["name"]
573            assert description == PetSchema.description[name]
574        post = p["post"]
575        post_schema = spec_fixture.marshmallow_plugin.resolver.resolve_schema_dict(
576            PetSchema
577        )
578        assert (
579            post["requestBody"]["content"]["application/json"]["schema"] == post_schema
580        )
581        assert post["requestBody"]["description"] == "a pet schema"
582        assert post["requestBody"]["required"]
583
584    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
585    def test_callback_schema_expand_parameters_v3(self, make_pet_callback_spec):
586        spec_fixture = make_pet_callback_spec(
587            {
588                "get": {"parameters": [{"in": "query", "schema": PetSchema}]},
589                "post": {
590                    "requestBody": {
591                        "description": "a pet schema",
592                        "required": True,
593                        "content": {"application/json": {"schema": PetSchema}},
594                    }
595                },
596            }
597        )
598        p = get_paths(spec_fixture.spec)["/pet"]
599        c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
600        get = c["get"]
601        assert get["parameters"] == spec_fixture.openapi.schema2parameters(
602            PetSchema(), location="query"
603        )
604        for parameter in get["parameters"]:
605            description = parameter.get("description", False)
606            assert description
607            name = parameter["name"]
608            assert description == PetSchema.description[name]
609        post = c["post"]
610        post_schema = spec_fixture.marshmallow_plugin.resolver.resolve_schema_dict(
611            PetSchema
612        )
613        assert (
614            post["requestBody"]["content"]["application/json"]["schema"] == post_schema
615        )
616        assert post["requestBody"]["description"] == "a pet schema"
617        assert post["requestBody"]["required"]
618
619    @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
620    def test_schema_uses_ref_if_available_v2(self, spec_fixture):
621        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
622        spec_fixture.spec.path(
623            path="/pet", operations={"get": {"responses": {200: {"schema": PetSchema}}}}
624        )
625        get = get_paths(spec_fixture.spec)["/pet"]["get"]
626        assert get["responses"]["200"]["schema"] == build_ref(
627            spec_fixture.spec, "schema", "Pet"
628        )
629
630    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
631    def test_schema_uses_ref_if_available_v3(self, spec_fixture):
632        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
633        spec_fixture.spec.path(
634            path="/pet",
635            operations={
636                "get": {
637                    "responses": {
638                        200: {"content": {"application/json": {"schema": PetSchema}}}
639                    }
640                }
641            },
642        )
643        get = get_paths(spec_fixture.spec)["/pet"]["get"]
644        assert get["responses"]["200"]["content"]["application/json"][
645            "schema"
646        ] == build_ref(spec_fixture.spec, "schema", "Pet")
647
648    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
649    def test_callback_schema_uses_ref_if_available_v3(self, make_pet_callback_spec):
650        spec_fixture = make_pet_callback_spec(
651            {
652                "get": {
653                    "responses": {
654                        "200": {"content": {"application/json": {"schema": PetSchema}}}
655                    }
656                }
657            }
658        )
659        p = get_paths(spec_fixture.spec)["/pet"]
660        c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
661        get = c["get"]
662        assert get["responses"]["200"]["content"]["application/json"][
663            "schema"
664        ] == build_ref(spec_fixture.spec, "schema", "Pet")
665
666    def test_schema_uses_ref_if_available_name_resolver_returns_none_v2(self):
667        def resolver(schema):
668            return None
669
670        spec = APISpec(
671            title="Test auto-reference",
672            version="0.1",
673            openapi_version="2.0",
674            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
675        )
676        spec.components.schema("Pet", schema=PetSchema)
677        spec.path(
678            path="/pet", operations={"get": {"responses": {200: {"schema": PetSchema}}}}
679        )
680        get = get_paths(spec)["/pet"]["get"]
681        assert get["responses"]["200"]["schema"] == build_ref(spec, "schema", "Pet")
682
683    def test_schema_uses_ref_if_available_name_resolver_returns_none_v3(self):
684        def resolver(schema):
685            return None
686
687        spec = APISpec(
688            title="Test auto-reference",
689            version="0.1",
690            openapi_version="3.0.0",
691            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
692        )
693        spec.components.schema("Pet", schema=PetSchema)
694        spec.path(
695            path="/pet",
696            operations={
697                "get": {
698                    "responses": {
699                        200: {"content": {"application/json": {"schema": PetSchema}}}
700                    }
701                }
702            },
703        )
704        get = get_paths(spec)["/pet"]["get"]
705        assert get["responses"]["200"]["content"]["application/json"][
706            "schema"
707        ] == build_ref(spec, "schema", "Pet")
708
709    @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
710    def test_schema_resolver_allof_v2(self, spec_fixture):
711        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
712        spec_fixture.spec.components.schema("Sample", schema=SampleSchema)
713        spec_fixture.spec.path(
714            path="/pet",
715            operations={
716                "get": {
717                    "responses": {200: {"schema": {"allOf": [PetSchema, SampleSchema]}}}
718                }
719            },
720        )
721        get = get_paths(spec_fixture.spec)["/pet"]["get"]
722        assert get["responses"]["200"]["schema"] == {
723            "allOf": [
724                build_ref(spec_fixture.spec, "schema", "Pet"),
725                build_ref(spec_fixture.spec, "schema", "Sample"),
726            ]
727        }
728
729    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
730    @pytest.mark.parametrize("combinator", ["oneOf", "anyOf", "allOf"])
731    def test_schema_resolver_oneof_anyof_allof_v3(self, spec_fixture, combinator):
732        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
733        spec_fixture.spec.path(
734            path="/pet",
735            operations={
736                "get": {
737                    "responses": {
738                        200: {
739                            "content": {
740                                "application/json": {
741                                    "schema": {combinator: [PetSchema, SampleSchema]}
742                                }
743                            }
744                        }
745                    }
746                }
747            },
748        )
749        get = get_paths(spec_fixture.spec)["/pet"]["get"]
750        assert get["responses"]["200"]["content"]["application/json"]["schema"] == {
751            combinator: [
752                build_ref(spec_fixture.spec, "schema", "Pet"),
753                build_ref(spec_fixture.spec, "schema", "Sample"),
754            ]
755        }
756
757    @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
758    def test_schema_resolver_not_v2(self, spec_fixture):
759        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
760        spec_fixture.spec.path(
761            path="/pet",
762            operations={"get": {"responses": {200: {"schema": {"not": PetSchema}}}}},
763        )
764        get = get_paths(spec_fixture.spec)["/pet"]["get"]
765        assert get["responses"]["200"]["schema"] == {
766            "not": build_ref(spec_fixture.spec, "schema", "Pet"),
767        }
768
769    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
770    def test_schema_resolver_not_v3(self, spec_fixture):
771        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
772        spec_fixture.spec.path(
773            path="/pet",
774            operations={
775                "get": {
776                    "responses": {
777                        200: {
778                            "content": {
779                                "application/json": {"schema": {"not": PetSchema}}
780                            }
781                        }
782                    }
783                }
784            },
785        )
786        get = get_paths(spec_fixture.spec)["/pet"]["get"]
787        assert get["responses"]["200"]["content"]["application/json"]["schema"] == {
788            "not": build_ref(spec_fixture.spec, "schema", "Pet"),
789        }
790
791    @pytest.mark.parametrize(
792        "pet_schema",
793        (PetSchema, PetSchema(), "tests.schemas.PetSchema"),
794    )
795    def test_schema_name_resolver_returns_none_v2(self, pet_schema):
796        def resolver(schema):
797            return None
798
799        spec = APISpec(
800            title="Test resolver returns None",
801            version="0.1",
802            openapi_version="2.0",
803            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
804        )
805        spec.path(
806            path="/pet",
807            operations={"get": {"responses": {200: {"schema": pet_schema}}}},
808        )
809        get = get_paths(spec)["/pet"]["get"]
810        assert "properties" in get["responses"]["200"]["schema"]
811
812    @pytest.mark.parametrize(
813        "pet_schema",
814        (PetSchema, PetSchema(), "tests.schemas.PetSchema"),
815    )
816    def test_schema_name_resolver_returns_none_v3(self, pet_schema):
817        def resolver(schema):
818            return None
819
820        spec = APISpec(
821            title="Test resolver returns None",
822            version="0.1",
823            openapi_version="3.0.0",
824            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
825        )
826        spec.path(
827            path="/pet",
828            operations={
829                "get": {
830                    "responses": {
831                        200: {"content": {"application/json": {"schema": pet_schema}}}
832                    }
833                }
834            },
835        )
836        get = get_paths(spec)["/pet"]["get"]
837        assert (
838            "properties"
839            in get["responses"]["200"]["content"]["application/json"]["schema"]
840        )
841
842    def test_callback_schema_uses_ref_if_available_name_resolver_returns_none_v3(self):
843        def resolver(schema):
844            return None
845
846        spec = APISpec(
847            title="Test auto-reference",
848            version="0.1",
849            openapi_version="3.0.0",
850            plugins=(MarshmallowPlugin(schema_name_resolver=resolver),),
851        )
852        spec.components.schema("Pet", schema=PetSchema)
853        spec.path(
854            path="/pet",
855            operations={
856                "post": {
857                    "callbacks": {
858                        "petEvent": {
859                            "petCallbackUrl": {
860                                "get": {
861                                    "responses": {
862                                        "200": {
863                                            "content": {
864                                                "application/json": {
865                                                    "schema": PetSchema
866                                                }
867                                            }
868                                        }
869                                    }
870                                }
871                            }
872                        }
873                    }
874                }
875            },
876        )
877        p = get_paths(spec)["/pet"]
878        c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
879        get = c["get"]
880        assert get["responses"]["200"]["content"]["application/json"][
881            "schema"
882        ] == build_ref(spec, "schema", "Pet")
883
884    @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
885    def test_schema_uses_ref_in_parameters_and_request_body_if_available_v2(
886        self, spec_fixture
887    ):
888        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
889        spec_fixture.spec.path(
890            path="/pet",
891            operations={
892                "get": {"parameters": [{"in": "query", "schema": PetSchema}]},
893                "post": {"parameters": [{"in": "body", "schema": PetSchema}]},
894            },
895        )
896        p = get_paths(spec_fixture.spec)["/pet"]
897        assert "schema" not in p["get"]["parameters"][0]
898        post = p["post"]
899        assert len(post["parameters"]) == 1
900        assert post["parameters"][0]["schema"] == build_ref(
901            spec_fixture.spec, "schema", "Pet"
902        )
903
904    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
905    def test_schema_uses_ref_in_parameters_and_request_body_if_available_v3(
906        self, spec_fixture
907    ):
908        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
909        spec_fixture.spec.path(
910            path="/pet",
911            operations={
912                "get": {"parameters": [{"in": "query", "schema": PetSchema}]},
913                "post": {
914                    "requestBody": {
915                        "content": {"application/json": {"schema": PetSchema}}
916                    }
917                },
918            },
919        )
920        p = get_paths(spec_fixture.spec)["/pet"]
921        assert "schema" in p["get"]["parameters"][0]
922        post = p["post"]
923        schema_ref = post["requestBody"]["content"]["application/json"]["schema"]
924        assert schema_ref == build_ref(spec_fixture.spec, "schema", "Pet")
925
926    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
927    def test_callback_schema_uses_ref_in_parameters_and_request_body_if_available_v3(
928        self, make_pet_callback_spec
929    ):
930        spec_fixture = make_pet_callback_spec(
931            {
932                "get": {"parameters": [{"in": "query", "schema": PetSchema}]},
933                "post": {
934                    "requestBody": {
935                        "content": {"application/json": {"schema": PetSchema}}
936                    }
937                },
938            }
939        )
940        p = get_paths(spec_fixture.spec)["/pet"]
941        c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
942        assert "schema" in c["get"]["parameters"][0]
943        post = c["post"]
944        schema_ref = post["requestBody"]["content"]["application/json"]["schema"]
945        assert schema_ref == build_ref(spec_fixture.spec, "schema", "Pet")
946
947    @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
948    def test_schema_array_uses_ref_if_available_v2(self, spec_fixture):
949        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
950        spec_fixture.spec.path(
951            path="/pet",
952            operations={
953                "get": {
954                    "parameters": [
955                        {
956                            "name": "petSchema",
957                            "in": "body",
958                            "schema": {"type": "array", "items": PetSchema},
959                        }
960                    ],
961                    "responses": {
962                        200: {"schema": {"type": "array", "items": PetSchema}}
963                    },
964                }
965            },
966        )
967        get = get_paths(spec_fixture.spec)["/pet"]["get"]
968        assert len(get["parameters"]) == 1
969        resolved_schema = {
970            "type": "array",
971            "items": build_ref(spec_fixture.spec, "schema", "Pet"),
972        }
973        assert get["parameters"][0]["schema"] == resolved_schema
974        assert get["responses"]["200"]["schema"] == resolved_schema
975
976    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
977    def test_schema_array_uses_ref_if_available_v3(self, spec_fixture):
978        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
979        spec_fixture.spec.path(
980            path="/pet",
981            operations={
982                "get": {
983                    "parameters": [
984                        {
985                            "name": "Pet",
986                            "in": "query",
987                            "content": {
988                                "application/json": {
989                                    "schema": {"type": "array", "items": PetSchema}
990                                }
991                            },
992                        }
993                    ],
994                    "responses": {
995                        200: {
996                            "content": {
997                                "application/json": {
998                                    "schema": {"type": "array", "items": PetSchema}
999                                }
1000                            }
1001                        }
1002                    },
1003                }
1004            },
1005        )
1006        get = get_paths(spec_fixture.spec)["/pet"]["get"]
1007        assert len(get["parameters"]) == 1
1008        resolved_schema = {
1009            "type": "array",
1010            "items": build_ref(spec_fixture.spec, "schema", "Pet"),
1011        }
1012        request_schema = get["parameters"][0]["content"]["application/json"]["schema"]
1013        assert request_schema == resolved_schema
1014        response_schema = get["responses"]["200"]["content"]["application/json"][
1015            "schema"
1016        ]
1017        assert response_schema == resolved_schema
1018
1019    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
1020    def test_callback_schema_array_uses_ref_if_available_v3(
1021        self, make_pet_callback_spec
1022    ):
1023        spec_fixture = make_pet_callback_spec(
1024            {
1025                "get": {
1026                    "parameters": [
1027                        {
1028                            "name": "Pet",
1029                            "in": "query",
1030                            "content": {
1031                                "application/json": {
1032                                    "schema": {"type": "array", "items": PetSchema}
1033                                }
1034                            },
1035                        }
1036                    ],
1037                    "responses": {
1038                        "200": {
1039                            "content": {
1040                                "application/json": {
1041                                    "schema": {"type": "array", "items": PetSchema}
1042                                }
1043                            }
1044                        }
1045                    },
1046                }
1047            }
1048        )
1049        p = get_paths(spec_fixture.spec)["/pet"]
1050        c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
1051        get = c["get"]
1052        assert len(get["parameters"]) == 1
1053        resolved_schema = {
1054            "type": "array",
1055            "items": build_ref(spec_fixture.spec, "schema", "Pet"),
1056        }
1057        request_schema = get["parameters"][0]["content"]["application/json"]["schema"]
1058        assert request_schema == resolved_schema
1059        response_schema = get["responses"]["200"]["content"]["application/json"][
1060            "schema"
1061        ]
1062        assert response_schema == resolved_schema
1063
1064    @pytest.mark.parametrize("spec_fixture", ("2.0",), indirect=True)
1065    def test_schema_partially_v2(self, spec_fixture):
1066        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
1067        spec_fixture.spec.path(
1068            path="/parents",
1069            operations={
1070                "get": {
1071                    "responses": {
1072                        200: {
1073                            "schema": {
1074                                "type": "object",
1075                                "properties": {
1076                                    "mother": PetSchema,
1077                                    "father": PetSchema,
1078                                },
1079                            }
1080                        }
1081                    }
1082                }
1083            },
1084        )
1085        get = get_paths(spec_fixture.spec)["/parents"]["get"]
1086        assert get["responses"]["200"]["schema"] == {
1087            "type": "object",
1088            "properties": {
1089                "mother": build_ref(spec_fixture.spec, "schema", "Pet"),
1090                "father": build_ref(spec_fixture.spec, "schema", "Pet"),
1091            },
1092        }
1093
1094    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
1095    def test_schema_partially_v3(self, spec_fixture):
1096        spec_fixture.spec.components.schema("Pet", schema=PetSchema)
1097        spec_fixture.spec.path(
1098            path="/parents",
1099            operations={
1100                "get": {
1101                    "responses": {
1102                        200: {
1103                            "content": {
1104                                "application/json": {
1105                                    "schema": {
1106                                        "type": "object",
1107                                        "properties": {
1108                                            "mother": PetSchema,
1109                                            "father": PetSchema,
1110                                        },
1111                                    }
1112                                }
1113                            }
1114                        }
1115                    }
1116                }
1117            },
1118        )
1119        get = get_paths(spec_fixture.spec)["/parents"]["get"]
1120        assert get["responses"]["200"]["content"]["application/json"]["schema"] == {
1121            "type": "object",
1122            "properties": {
1123                "mother": build_ref(spec_fixture.spec, "schema", "Pet"),
1124                "father": build_ref(spec_fixture.spec, "schema", "Pet"),
1125            },
1126        }
1127
1128    @pytest.mark.parametrize("spec_fixture", ("3.0.0",), indirect=True)
1129    def test_callback_schema_partially_v3(self, make_pet_callback_spec):
1130        spec_fixture = make_pet_callback_spec(
1131            {
1132                "get": {
1133                    "responses": {
1134                        "200": {
1135                            "content": {
1136                                "application/json": {
1137                                    "schema": {
1138                                        "type": "object",
1139                                        "properties": {
1140                                            "mother": PetSchema,
1141                                            "father": PetSchema,
1142                                        },
1143                                    }
1144                                }
1145                            }
1146                        }
1147                    }
1148                }
1149            }
1150        )
1151        p = get_paths(spec_fixture.spec)["/pet"]
1152        c = p["post"]["callbacks"]["petEvent"]["petCallbackUrl"]
1153        get = c["get"]
1154        assert get["responses"]["200"]["content"]["application/json"]["schema"] == {
1155            "type": "object",
1156            "properties": {
1157                "mother": build_ref(spec_fixture.spec, "schema", "Pet"),
1158                "father": build_ref(spec_fixture.spec, "schema", "Pet"),
1159            },
1160        }
1161
1162    def test_parameter_reference(self, spec_fixture):
1163        if spec_fixture.spec.openapi_version.major < 3:
1164            param = {"schema": PetSchema}
1165        else:
1166            param = {"content": {"application/json": {"schema": PetSchema}}}
1167        spec_fixture.spec.components.parameter("Pet", "body", param)
1168        spec_fixture.spec.path(
1169            path="/parents", operations={"get": {"parameters": ["Pet"]}}
1170        )
1171        get = get_paths(spec_fixture.spec)["/parents"]["get"]
1172        assert get["parameters"] == [build_ref(spec_fixture.spec, "parameter", "Pet")]
1173
1174    def test_response_reference(self, spec_fixture):
1175        if spec_fixture.spec.openapi_version.major < 3:
1176            resp = {"schema": PetSchema}
1177        else:
1178            resp = {"content": {"application/json": {"schema": PetSchema}}}
1179        spec_fixture.spec.components.response("Pet", resp)
1180        spec_fixture.spec.path(
1181            path="/parents", operations={"get": {"responses": {"200": "Pet"}}}
1182        )
1183        get = get_paths(spec_fixture.spec)["/parents"]["get"]
1184        assert get["responses"] == {
1185            "200": build_ref(spec_fixture.spec, "response", "Pet")
1186        }
1187
1188    def test_schema_global_state_untouched_2json(self, spec_fixture):
1189        assert get_nested_schema(RunSchema, "sample") is None
1190        data = spec_fixture.openapi.schema2jsonschema(RunSchema)
1191        json.dumps(data)
1192        assert get_nested_schema(RunSchema, "sample") is None
1193
1194    def test_schema_global_state_untouched_2parameters(self, spec_fixture):
1195        assert get_nested_schema(RunSchema, "sample") is None
1196        data = spec_fixture.openapi.schema2parameters(RunSchema, location="json")
1197        json.dumps(data)
1198        assert get_nested_schema(RunSchema, "sample") is None
1199
1200    def test_resolve_schema_dict_ref_as_string(self, spec):
1201        """Test schema ref passed as string"""
1202        # The case tested here is a reference passed as string, not a
1203        # marshmallow Schema passed by name as string. We want to ensure the
1204        # MarshmallowPlugin does not interfere with the feature interpreting
1205        # strings as references. Therefore, we use a specific name to ensure
1206        # there is no Schema with that name in the marshmallow registry from
1207        # somewhere else in the tests.
1208        # e.g. PetSchema is in the registry already so it wouldn't work.
1209        schema = {"schema": "SomeSpecificPetSchema"}
1210        if spec.openapi_version.major >= 3:
1211            schema = {"content": {"application/json": schema}}
1212        spec.path("/pet/{petId}", operations={"get": {"responses": {"200": schema}}})
1213        resp = get_paths(spec)["/pet/{petId}"]["get"]["responses"]["200"]
1214        if spec.openapi_version.major < 3:
1215            schema = resp["schema"]
1216        else:
1217            schema = resp["content"]["application/json"]["schema"]
1218        assert schema == build_ref(spec, "schema", "SomeSpecificPetSchema")
1219
1220
1221class TestCircularReference:
1222    def test_circular_referencing_schemas(self, spec):
1223        spec.components.schema("Analysis", schema=AnalysisSchema)
1224        definitions = get_schemas(spec)
1225        ref = definitions["Analysis"]["properties"]["sample"]
1226        assert ref == build_ref(spec, "schema", "Sample")
1227
1228
1229# Regression tests for issue #55
1230class TestSelfReference:
1231    def test_self_referencing_field_single(self, spec):
1232        spec.components.schema("SelfReference", schema=SelfReferencingSchema)
1233        definitions = get_schemas(spec)
1234        ref = definitions["SelfReference"]["properties"]["single"]
1235        assert ref == build_ref(spec, "schema", "SelfReference")
1236
1237    def test_self_referencing_field_many(self, spec):
1238        spec.components.schema("SelfReference", schema=SelfReferencingSchema)
1239        definitions = get_schemas(spec)
1240        result = definitions["SelfReference"]["properties"]["many"]
1241        assert result == {
1242            "type": "array",
1243            "items": build_ref(spec, "schema", "SelfReference"),
1244        }
1245
1246
1247class TestOrderedSchema:
1248    def test_ordered_schema(self, spec):
1249        spec.components.schema("Ordered", schema=OrderedSchema)
1250        result = get_schemas(spec)["Ordered"]["properties"]
1251        assert list(result.keys()) == ["field1", "field2", "field3", "field4", "field5"]
1252
1253
1254class TestFieldWithCustomProps:
1255    def test_field_with_custom_props(self, spec):
1256        spec.components.schema("PatternedObject", schema=PatternedObjectSchema)
1257        result = get_schemas(spec)["PatternedObject"]["properties"]["count"]
1258        assert "x-count" in result
1259        assert result["x-count"] == 1
1260
1261    def test_field_with_custom_props_passed_as_snake_case(self, spec):
1262        spec.components.schema("PatternedObject", schema=PatternedObjectSchema)
1263        result = get_schemas(spec)["PatternedObject"]["properties"]["count2"]
1264        assert "x-count2" in result
1265        assert result["x-count2"] == 2
1266
1267
1268class TestSchemaWithDefaultValues:
1269    def test_schema_with_default_values(self, spec):
1270        spec.components.schema("DefaultValuesSchema", schema=DefaultValuesSchema)
1271        definitions = get_schemas(spec)
1272        props = definitions["DefaultValuesSchema"]["properties"]
1273        assert props["number_auto_default"]["default"] == 12
1274        assert props["number_manual_default"]["default"] == 42
1275        assert "default" not in props["string_callable_default"]
1276        assert props["string_manual_default"]["default"] == "Manual"
1277        assert "default" not in props["numbers"]
1278
1279
1280class TestDictValues:
1281    def test_dict_values_resolve_to_additional_properties(self, spec):
1282        class SchemaWithDict(Schema):
1283            dict_field = Dict(values=String())
1284
1285        spec.components.schema("SchemaWithDict", schema=SchemaWithDict)
1286        result = get_schemas(spec)["SchemaWithDict"]["properties"]["dict_field"]
1287        assert result == {"type": "object", "additionalProperties": {"type": "string"}}
1288
1289    def test_dict_with_empty_values_field(self, spec):
1290        class SchemaWithDict(Schema):
1291            dict_field = Dict()
1292
1293        spec.components.schema("SchemaWithDict", schema=SchemaWithDict)
1294        result = get_schemas(spec)["SchemaWithDict"]["properties"]["dict_field"]
1295        assert result == {"type": "object"}
1296
1297    def test_dict_with_nested(self, spec):
1298        class SchemaWithDict(Schema):
1299            dict_field = Dict(values=Nested(PetSchema))
1300
1301        spec.components.schema("SchemaWithDict", schema=SchemaWithDict)
1302
1303        assert len(get_schemas(spec)) == 2
1304
1305        result = get_schemas(spec)["SchemaWithDict"]["properties"]["dict_field"]
1306        assert result == {
1307            "additionalProperties": build_ref(spec, "schema", "Pet"),
1308            "type": "object",
1309        }
1310
1311
1312class TestList:
1313    def test_list_with_nested(self, spec):
1314        class SchemaWithList(Schema):
1315            list_field = List(Nested(PetSchema))
1316
1317        spec.components.schema("SchemaWithList", schema=SchemaWithList)
1318
1319        assert len(get_schemas(spec)) == 2
1320
1321        result = get_schemas(spec)["SchemaWithList"]["properties"]["list_field"]
1322        assert result == {"items": build_ref(spec, "schema", "Pet"), "type": "array"}
1323
1324
1325class TestTimeDelta:
1326    def test_timedelta_x_unit(self, spec):
1327        class SchemaWithTimeDelta(Schema):
1328            sec = TimeDelta("seconds")
1329            day = TimeDelta("days")
1330
1331        spec.components.schema("SchemaWithTimeDelta", schema=SchemaWithTimeDelta)
1332
1333        assert (
1334            get_schemas(spec)["SchemaWithTimeDelta"]["properties"]["sec"]["x-unit"]
1335            == "seconds"
1336        )
1337        assert (
1338            get_schemas(spec)["SchemaWithTimeDelta"]["properties"]["day"]["x-unit"]
1339            == "days"
1340        )
1341