]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
:arrow_up: Add tests, fix issues and update Pydantic
authorSebastián Ramírez <tiangolo@gmail.com>
Fri, 28 Dec 2018 12:10:29 +0000 (16:10 +0400)
committerSebastián Ramírez <tiangolo@gmail.com>
Fri, 28 Dec 2018 12:10:29 +0000 (16:10 +0400)
28 files changed:
docs/src/dependencies/tutorial002.py
docs/src/dependencies/tutorial003.py
docs/src/dependencies/tutorial004.py
docs/src/query_params/tutorial001.py
fastapi/dependencies/utils.py
fastapi/openapi/utils.py
pyproject.toml
tests/test_param_class.py [new file with mode: 0644]
tests/test_tutorial/test_application_configuration/test_tutorial001.py
tests/test_tutorial/test_body/test_tutorial001.py
tests/test_tutorial/test_body_multiple_params/test_tutorial001.py
tests/test_tutorial/test_custom_response/test_tutorial001.py
tests/test_tutorial/test_custom_response/test_tutorial004.py
tests/test_tutorial/test_dependencies/test_tutorial004.py [new file with mode: 0644]
tests/test_tutorial/test_extra_data_types/test_tutorial001.py
tests/test_tutorial/test_path_operation_advanced_configurations/__init__.py [new file with mode: 0644]
tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py [new file with mode: 0644]
tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py [new file with mode: 0644]
tests/test_tutorial/test_path_operation_configurations/__init__.py [new file with mode: 0644]
tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py [new file with mode: 0644]
tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py [new file with mode: 0644]
tests/test_tutorial/test_query_params_str_validations/test_tutorial001.py
tests/test_tutorial/test_request_files/test_tutorial001.py
tests/test_tutorial/test_request_forms/test_tutorial001.py
tests/test_tutorial/test_request_forms_and_files/test_tutorial001.py
tests/test_tutorial/test_response_model/test_tutorial003.py
tests/test_tutorial/test_security/test_tutorial001.py
tests/test_tutorial/test_security/test_tutorial003.py [new file with mode: 0644]

index 3afa89706abd04f99614ab485fb4c16bb62b96e8..9733c60c8bbcbbdb9cae8103520e690827cc4bc5 100644 (file)
@@ -18,6 +18,6 @@ async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
     response = {}
     if commons.q:
         response.update({"q": commons.q})
-    items = fake_items_db[commons.skip : commons.limit]
+    items = fake_items_db[commons.skip : commons.skip + commons.limit]
     response.update({"items": items})
     return response
index b5816285caae2a79a8e4ceae6b3ac1c821c99385..3f73619682dea9e10515f9b6753563575c654860 100644 (file)
@@ -18,6 +18,6 @@ async def read_items(commons=Depends(CommonQueryParams)):
     response = {}
     if commons.q:
         response.update({"q": commons.q})
-    items = fake_items_db[commons.skip : commons.limit]
+    items = fake_items_db[commons.skip : commons.skip + commons.limit]
     response.update({"items": items})
     return response
index 8b1e00e6b60b23f476aea24140a6ab4ee6e7ca94..6c7ca482109f649f6db88cad15f584cf18fd79ad 100644 (file)
@@ -18,6 +18,6 @@ async def read_items(commons: CommonQueryParams = Depends()):
     response = {}
     if commons.q:
         response.update({"q": commons.q})
-    items = fake_items_db[commons.skip : commons.limit]
+    items = fake_items_db[commons.skip : commons.skip + commons.limit]
     response.update({"items": items})
     return response
index 690c35461c4f4a02c2fc0ee4b38ad2e7cfa48080..2aa61ebfbf8d9790565c19a30741ab5affa8067a 100644 (file)
@@ -7,4 +7,4 @@ fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"
 
 @app.get("/items/")
 async def read_item(skip: int = 0, limit: int = 100):
-    return fake_items_db[skip:limit]
+    return fake_items_db[skip : skip + limit]
index c183b8971795a973f2c212b33b2d4ea0af23eb9d..9cb9f3f14edfe566ff40b44831b6384b72544d86 100644 (file)
@@ -58,8 +58,6 @@ def get_flat_dependant(dependant: Dependant) -> Dependant:
         security_schemes=dependant.security_requirements.copy(),
     )
     for sub_dependant in dependant.dependencies:
-        if sub_dependant is dependant:
-            raise ValueError("recursion", dependant.dependencies)
         flat_sub = get_flat_dependant(sub_dependant)
         flat_dependant.path_params.extend(flat_sub.path_params)
         flat_dependant.query_params.extend(flat_sub.query_params)
@@ -197,16 +195,12 @@ def add_param_to_body_fields(*, param: inspect.Parameter, dependant: Dependant)
     dependant.body_params.append(field)
 
 
-def is_coroutine_callable(call: Callable = None) -> bool:
-    if not call:
-        return False
+def is_coroutine_callable(call: Callable) -> bool:
     if inspect.isfunction(call):
         return asyncio.iscoroutinefunction(call)
     if inspect.isclass(call):
         return False
     call = getattr(call, "__call__", None)
-    if not call:
-        return False
     return asyncio.iscoroutinefunction(call)
 
 
index 0f0a03624952273c45d7762fe893bdbf04c5c00e..d681088e5297ae7e71dfca2b70f1a5d9cd044739 100644 (file)
@@ -147,61 +147,65 @@ def get_openapi_path(
     security_schemes: Dict[str, Any] = {}
     definitions: Dict[str, Any] = {}
     assert route.methods is not None, "Methods must be a list"
-    for method in route.methods:
-        operation = get_openapi_operation_metadata(route=route, method=method)
-        parameters: List[Dict] = []
-        flat_dependant = get_flat_dependant(route.dependant)
-        security_definitions, operation_security = get_openapi_security_definitions(
-            flat_dependant=flat_dependant
-        )
-        if operation_security:
-            operation.setdefault("security", []).extend(operation_security)
-        if security_definitions:
-            security_schemes.update(security_definitions)
-        all_route_params = get_openapi_params(route.dependant)
-        validation_definitions, operation_parameters = get_openapi_operation_parameters(
-            all_route_params=all_route_params
-        )
-        definitions.update(validation_definitions)
-        parameters.extend(operation_parameters)
-        if parameters:
-            operation["parameters"] = parameters
-        if method in METHODS_WITH_BODY:
-            request_body_oai = get_openapi_operation_request_body(
-                body_field=route.body_field, model_name_map=model_name_map
+    if route.include_in_schema:
+        for method in route.methods:
+            operation = get_openapi_operation_metadata(route=route, method=method)
+            parameters: List[Dict] = []
+            flat_dependant = get_flat_dependant(route.dependant)
+            security_definitions, operation_security = get_openapi_security_definitions(
+                flat_dependant=flat_dependant
+            )
+            if operation_security:
+                operation.setdefault("security", []).extend(operation_security)
+            if security_definitions:
+                security_schemes.update(security_definitions)
+            all_route_params = get_openapi_params(route.dependant)
+            validation_definitions, operation_parameters = get_openapi_operation_parameters(
+                all_route_params=all_route_params
             )
-            if request_body_oai:
-                operation["requestBody"] = request_body_oai
-                if "ValidationError" not in definitions:
-                    definitions["ValidationError"] = validation_error_definition
-                    definitions[
-                        "HTTPValidationError"
-                    ] = validation_error_response_definition
-        status_code = str(route.status_code)
-        response_schema = {"type": "string"}
-        if lenient_issubclass(route.content_type, JSONResponse):
-            if route.response_field:
-                response_schema, _ = field_schema(
-                    route.response_field,
-                    model_name_map=model_name_map,
-                    ref_prefix=REF_PREFIX,
+            definitions.update(validation_definitions)
+            parameters.extend(operation_parameters)
+            if parameters:
+                operation["parameters"] = parameters
+            if method in METHODS_WITH_BODY:
+                request_body_oai = get_openapi_operation_request_body(
+                    body_field=route.body_field, model_name_map=model_name_map
                 )
-            else:
-                response_schema = {}
-        content = {route.content_type.media_type: {"schema": response_schema}}
-        operation["responses"] = {
-            status_code: {"description": route.response_description, "content": content}
-        }
-        if all_route_params or route.body_field:
-            operation["responses"][str(HTTP_422_UNPROCESSABLE_ENTITY)] = {
-                "description": "Validation Error",
-                "content": {
-                    "application/json": {
-                        "schema": {"$ref": REF_PREFIX + "HTTPValidationError"}
-                    }
-                },
+                if request_body_oai:
+                    operation["requestBody"] = request_body_oai
+                    if "ValidationError" not in definitions:
+                        definitions["ValidationError"] = validation_error_definition
+                        definitions[
+                            "HTTPValidationError"
+                        ] = validation_error_response_definition
+            status_code = str(route.status_code)
+            response_schema = {"type": "string"}
+            if lenient_issubclass(route.content_type, JSONResponse):
+                if route.response_field:
+                    response_schema, _ = field_schema(
+                        route.response_field,
+                        model_name_map=model_name_map,
+                        ref_prefix=REF_PREFIX,
+                    )
+                else:
+                    response_schema = {}
+            content = {route.content_type.media_type: {"schema": response_schema}}
+            operation["responses"] = {
+                status_code: {
+                    "description": route.response_description,
+                    "content": content,
+                }
             }
-        path[method.lower()] = operation
+            if all_route_params or route.body_field:
+                operation["responses"][str(HTTP_422_UNPROCESSABLE_ENTITY)] = {
+                    "description": "Validation Error",
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": REF_PREFIX + "HTTPValidationError"}
+                        }
+                    },
+                }
+            path[method.lower()] = operation
     return path, security_schemes, definitions
 
 
index e0a7b93ae2cdcdcfa979ac110b8a8c5103f14372..5b26eea7fb3693f4808a6fff87a901839a9580f5 100644 (file)
@@ -20,7 +20,7 @@ classifiers = [
 ]
 requires = [
     "starlette >=0.9.7",
-    "pydantic >=0.16"
+    "pydantic >=0.17"
 ]
 description-file = "README.md"
 requires-python = ">=3.6"
diff --git a/tests/test_param_class.py b/tests/test_param_class.py
new file mode 100644 (file)
index 0000000..8a3d625
--- /dev/null
@@ -0,0 +1,25 @@
+from fastapi import FastAPI
+from fastapi.params import Param
+from starlette.testclient import TestClient
+
+app = FastAPI()
+
+
+@app.get("/items/")
+def read_items(q: str = Param(None)):
+    return {"q": q}
+
+
+client = TestClient(app)
+
+
+def test_default_param_query_none():
+    response = client.get("/items/")
+    assert response.status_code == 200
+    assert response.json() == {"q": None}
+
+
+def test_default_param_query():
+    response = client.get("/items/?q=foo")
+    assert response.status_code == 200
+    assert response.json() == {"q": "foo"}
index 3f5b7fbda39ea95ef60651f76c77f60ef5ab8db5..577e9829cd83c22ec6e468b5088aaa89a2193919 100644 (file)
@@ -28,7 +28,13 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
+
+
+def test_items():
+    response = client.get("/items/")
+    assert response.status_code == 200
+    assert response.json() == [{"name": "Foo"}]
index ee06de7cebe26af4cbd243a2b16dab74daa4a706..4c71d1dcd4be2eadb3736bca8ce95287ae274a46 100644 (file)
@@ -83,7 +83,7 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
index 8a2b1da7fd6d7c047666018ff7c1652f815e090a..e0e6237e76710a8609b2003e39032dcfbb4511b4 100644 (file)
@@ -101,7 +101,7 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
index 7801326f8593fb6e59d065693de2a81cea486d1f..c13602e4acfbea9405c8314246eb4608b9389ca1 100644 (file)
@@ -24,7 +24,7 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
index 99431ea3f9b9c4adb5119ea4d958c2e9213f4cba..0d4c2e46d617482936d02ef2a6048f856d64f51a 100644 (file)
@@ -35,7 +35,7 @@ html_contents = """
     """
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
diff --git a/tests/test_tutorial/test_dependencies/test_tutorial004.py b/tests/test_tutorial/test_dependencies/test_tutorial004.py
new file mode 100644 (file)
index 0000000..b55e780
--- /dev/null
@@ -0,0 +1,144 @@
+import pytest
+from starlette.testclient import TestClient
+
+from dependencies.tutorial004 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "Fast API", "version": "0.1.0"},
+    "paths": {
+        "/items/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Read Items Get",
+                "operationId": "read_items_items__get",
+                "parameters": [
+                    {
+                        "required": False,
+                        "schema": {"title": "Q", "type": "string"},
+                        "name": "q",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Skip", "type": "integer", "default": 0},
+                        "name": "skip",
+                        "in": "query",
+                    },
+                    {
+                        "required": False,
+                        "schema": {"title": "Limit", "type": "integer", "default": 100},
+                        "name": "limit",
+                        "in": "query",
+                    },
+                ],
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+        }
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_schema
+
+
+@pytest.mark.parametrize(
+    "path,expected_status,expected_response",
+    [
+        (
+            "/items",
+            200,
+            {
+                "items": [
+                    {"item_name": "Foo"},
+                    {"item_name": "Bar"},
+                    {"item_name": "Baz"},
+                ]
+            },
+        ),
+        (
+            "/items?q=foo",
+            200,
+            {
+                "items": [
+                    {"item_name": "Foo"},
+                    {"item_name": "Bar"},
+                    {"item_name": "Baz"},
+                ],
+                "q": "foo",
+            },
+        ),
+        (
+            "/items?q=foo&skip=1",
+            200,
+            {"items": [{"item_name": "Bar"}, {"item_name": "Baz"}], "q": "foo"},
+        ),
+        (
+            "/items?q=bar&limit=2",
+            200,
+            {"items": [{"item_name": "Foo"}, {"item_name": "Bar"}], "q": "bar"},
+        ),
+        (
+            "/items?q=bar&skip=1&limit=1",
+            200,
+            {"items": [{"item_name": "Bar"}], "q": "bar"},
+        ),
+        (
+            "/items?limit=1&q=bar&skip=1",
+            200,
+            {"items": [{"item_name": "Bar"}], "q": "bar"},
+        ),
+    ],
+)
+def test_get(path, expected_status, expected_response):
+    response = client.get(path)
+    assert response.status_code == expected_status
+    assert response.json() == expected_response
index be05be662e9986e435f8c6b913e103c7ef294160..c925a9af65dfeb927196aca1899840da5cf2371e 100644 (file)
@@ -74,7 +74,7 @@ openapi_schema = {
                     },
                     "process_after": {
                         "title": "Process_After",
-                        "type": "string",
+                        "type": "number",
                         "format": "time-delta",
                     },
                 },
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/__init__.py b/tests/test_tutorial/test_path_operation_advanced_configurations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial001.py
new file mode 100644 (file)
index 0000000..872fd6b
--- /dev/null
@@ -0,0 +1,36 @@
+from starlette.testclient import TestClient
+
+from path_operation_advanced_configuration.tutorial001 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "Fast API", "version": "0.1.0"},
+    "paths": {
+        "/items/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "summary": "Read Items Get",
+                "operationId": "some_specific_id_you_define",
+            }
+        }
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_schema
+
+
+def test_get():
+    response = client.get("/items/")
+    assert response.status_code == 200
+    assert response.json() == [{"item_id": "Foo"}]
diff --git a/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py b/tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial002.py
new file mode 100644 (file)
index 0000000..7818a0b
--- /dev/null
@@ -0,0 +1,23 @@
+from starlette.testclient import TestClient
+
+from path_operation_advanced_configuration.tutorial002 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "Fast API", "version": "0.1.0"},
+    "paths": {},
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_schema
+
+
+def test_get():
+    response = client.get("/items/")
+    assert response.status_code == 200
+    assert response.json() == [{"item_id": "Foo"}]
diff --git a/tests/test_tutorial/test_path_operation_configurations/__init__.py b/tests/test_tutorial/test_path_operation_configurations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
new file mode 100644 (file)
index 0000000..debe47a
--- /dev/null
@@ -0,0 +1,112 @@
+from starlette.testclient import TestClient
+
+from path_operation_configuration.tutorial005 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "Fast API", "version": "0.1.0"},
+    "paths": {
+        "/items/": {
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "The created item",
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/Item"}
+                            }
+                        },
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Create an item",
+                "description": "\n    Create an item with all the information:\n    \n    * name: each item must have a name\n    * description: a long description\n    * price: required\n    * tax: if the item doesn't have tax, you can omit this\n    * tags: a set of unique tag strings for this item\n    ",
+                "operationId": "create_item_items__post",
+                "requestBody": {
+                    "content": {
+                        "application/json": {
+                            "schema": {"$ref": "#/components/schemas/Item"}
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+            "Item": {
+                "title": "Item",
+                "required": ["name", "price"],
+                "type": "object",
+                "properties": {
+                    "name": {"title": "Name", "type": "string"},
+                    "price": {"title": "Price", "type": "number"},
+                    "description": {"title": "Description", "type": "string"},
+                    "tax": {"title": "Tax", "type": "number"},
+                    "tags": {
+                        "title": "Tags",
+                        "uniqueItems": True,
+                        "type": "array",
+                        "items": {"type": "string"},
+                        "default": [],
+                    },
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+        }
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_schema
+
+
+def test_query_params_str_validations():
+    response = client.post("/items/", json={"name": "Foo", "price": 42})
+    assert response.status_code == 200
+    assert response.json() == {
+        "name": "Foo",
+        "price": 42,
+        "description": None,
+        "tax": None,
+        "tags": [],
+    }
diff --git a/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py b/tests/test_tutorial/test_path_operation_configurations/test_tutorial006.py
new file mode 100644 (file)
index 0000000..2ddfcdd
--- /dev/null
@@ -0,0 +1,73 @@
+import pytest
+from starlette.testclient import TestClient
+
+from path_operation_configuration.tutorial006 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "Fast API", "version": "0.1.0"},
+    "paths": {
+        "/items/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "tags": ["items"],
+                "summary": "Read Items Get",
+                "operationId": "read_items_items__get",
+            }
+        },
+        "/users/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "tags": ["users"],
+                "summary": "Read Users Get",
+                "operationId": "read_users_users__get",
+            }
+        },
+        "/elements/": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "tags": ["items"],
+                "summary": "Read Elements Get",
+                "operationId": "read_elements_elements__get",
+                "deprecated": True,
+            }
+        },
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_schema
+
+
+@pytest.mark.parametrize(
+    "path,expected_status,expected_response",
+    [
+        ("/items/", 200, [{"name": "Foo", "price": 42}]),
+        ("/users/", 200, [{"username": "johndoe"}]),
+        ("/elements/", 200, [{"item_id": "Foo"}]),
+    ],
+)
+def test_query_params_str_validations(path, expected_status, expected_response):
+    response = client.get(path)
+    assert response.status_code == expected_status
+    assert response.json() == expected_response
index 5c8ecb24292e45ae14c3cff76566019d7b84e74e..47c88e523cf71dc5f3ec825871be4c2551e51aa1 100644 (file)
@@ -1,3 +1,4 @@
+import pytest
 from starlette.testclient import TestClient
 
 from query_params_str_validations.tutorial010 import app
@@ -80,7 +81,42 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
+
+
+regex_error = {
+    "detail": [
+        {
+            "ctx": {"pattern": "^fixedquery$"},
+            "loc": ["query", "item-query"],
+            "msg": 'string does not match regex "^fixedquery$"',
+            "type": "value_error.str.regex",
+        }
+    ]
+}
+
+
+@pytest.mark.parametrize(
+    "q_name,q,expected_status,expected_response",
+    [
+        (None, None, 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}),
+        (
+            "item-query",
+            "fixedquery",
+            200,
+            {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}], "q": "fixedquery"},
+        ),
+        ("q", "fixedquery", 200, {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}),
+        ("item-query", "nonregexquery", 422, regex_error),
+    ],
+)
+def test_query_params_str_validations(q_name, q, expected_status, expected_response):
+    url = "/items/"
+    if q_name and q:
+        url = f"{url}?{q_name}={q}"
+    response = client.get(url)
+    assert response.status_code == expected_status
+    assert response.json() == expected_response
index 6e20dd911c900deda3ead7c1e95c426aee94024b..d92d5a749f2e9b80bf30519d2ed928e305a941b7 100644 (file)
@@ -81,7 +81,7 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
index f80490df5c33f173aca2b9234b9b5e3023d7c9d2..bd3bfc3c8b148d08b2cf37754da00d5483acfb7c 100644 (file)
@@ -81,7 +81,7 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
index 968bbe565b102d14012e4373673e56997e0ab4a4..5e344482c8db6b45299509bb044be022c19a6714 100644 (file)
@@ -82,7 +82,7 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
index b91715a77af50632d8f3bed26cb23856e2702119..52d54f913da4cf97783fa6b1a593f0e264abaf7e 100644 (file)
@@ -96,7 +96,7 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
index 7c73a6654f0c141ebfeb9cd256e95454fac2cb45..d6db2743020f12bdc85c76256b197b9f27bbdaad 100644 (file)
@@ -33,7 +33,7 @@ openapi_schema = {
 }
 
 
-def test_openapi_scheme():
+def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
     assert response.json() == openapi_schema
diff --git a/tests/test_tutorial/test_security/test_tutorial003.py b/tests/test_tutorial/test_security/test_tutorial003.py
new file mode 100644 (file)
index 0000000..248fa0e
--- /dev/null
@@ -0,0 +1,167 @@
+from starlette.testclient import TestClient
+
+from security.tutorial003 import app
+
+client = TestClient(app)
+
+openapi_schema = {
+    "openapi": "3.0.2",
+    "info": {"title": "Fast API", "version": "0.1.0"},
+    "paths": {
+        "/token": {
+            "post": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    },
+                    "422": {
+                        "description": "Validation Error",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": "#/components/schemas/HTTPValidationError"
+                                }
+                            }
+                        },
+                    },
+                },
+                "summary": "Login Post",
+                "operationId": "login_token_post",
+                "requestBody": {
+                    "content": {
+                        "application/x-www-form-urlencoded": {
+                            "schema": {"$ref": "#/components/schemas/Body_login"}
+                        }
+                    },
+                    "required": True,
+                },
+            }
+        },
+        "/users/me": {
+            "get": {
+                "responses": {
+                    "200": {
+                        "description": "Successful Response",
+                        "content": {"application/json": {"schema": {}}},
+                    }
+                },
+                "summary": "Read Users Me Get",
+                "operationId": "read_users_me_users_me_get",
+                "security": [{"OAuth2PasswordBearer": []}],
+            }
+        },
+    },
+    "components": {
+        "schemas": {
+            "Body_login": {
+                "title": "Body_login",
+                "required": ["username", "password"],
+                "type": "object",
+                "properties": {
+                    "grant_type": {
+                        "title": "Grant_Type",
+                        "pattern": "password",
+                        "type": "string",
+                    },
+                    "username": {"title": "Username", "type": "string"},
+                    "password": {"title": "Password", "type": "string"},
+                    "scope": {"title": "Scope", "type": "string", "default": ""},
+                    "client_id": {"title": "Client_Id", "type": "string"},
+                    "client_secret": {"title": "Client_Secret", "type": "string"},
+                },
+            },
+            "ValidationError": {
+                "title": "ValidationError",
+                "required": ["loc", "msg", "type"],
+                "type": "object",
+                "properties": {
+                    "loc": {
+                        "title": "Location",
+                        "type": "array",
+                        "items": {"type": "string"},
+                    },
+                    "msg": {"title": "Message", "type": "string"},
+                    "type": {"title": "Error Type", "type": "string"},
+                },
+            },
+            "HTTPValidationError": {
+                "title": "HTTPValidationError",
+                "type": "object",
+                "properties": {
+                    "detail": {
+                        "title": "Detail",
+                        "type": "array",
+                        "items": {"$ref": "#/components/schemas/ValidationError"},
+                    }
+                },
+            },
+        },
+        "securitySchemes": {
+            "OAuth2PasswordBearer": {
+                "type": "oauth2",
+                "flows": {"password": {"scopes": {}, "tokenUrl": "/token"}},
+            }
+        },
+    },
+}
+
+
+def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == openapi_schema
+
+
+def test_login():
+    response = client.post("/token", data={"username": "johndoe", "password": "secret"})
+    assert response.status_code == 200
+    assert response.json() == {"access_token": "johndoe", "token_type": "bearer"}
+
+
+def test_login_incorrect_password():
+    response = client.post("/token", data={"username": "johndoe", "password": "incorrect"})
+    assert response.status_code == 400
+    assert response.json() == {"detail": "Incorrect username or password"}
+
+
+def test_login_incorrect_username():
+    response = client.post("/token", data={"username": "foo", "password": "secret"})
+    assert response.status_code == 400
+    assert response.json() == {"detail": "Incorrect username or password"}
+
+
+def test_no_token():
+    response = client.get("/users/me")
+    assert response.status_code == 403
+    assert response.json() == {"detail": "Not authenticated"}
+
+
+def test_token():
+    response = client.get("/users/me", headers={"Authorization": "Bearer johndoe"})
+    assert response.status_code == 200
+    assert response.json() == {
+        "username": "johndoe",
+        "full_name": "John Doe",
+        "email": "johndoe@example.com",
+        "hashed_password": "fakehashedsecret",
+        "disabled": False,
+    }
+
+
+def test_incorrect_token():
+    response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"})
+    assert response.status_code == 400
+    assert response.json() == {"detail": "Invalid authentication credentials"}
+
+def test_incorrect_token_type():
+    response = client.get(
+        "/users/me", headers={"Authorization": "Notexistent testtoken"}
+    )
+    assert response.status_code == 403
+    assert response.json() == {"detail": "Not authenticated"}
+
+def test_inactive_user():
+    response = client.get("/users/me", headers={"Authorization": "Bearer alice"})
+    assert response.status_code == 400
+    assert response.json() == {"detail": "Inactive user"}