diff --git a/tests/test_http_query_method.py b/tests/test_http_query_method.py new file mode 100644 index 000000000..7006ec851 --- /dev/null +++ b/tests/test_http_query_method.py @@ -0,0 +1,288 @@ +from typing import Optional + +from dirty_equals import IsDict +from fastapi import APIRouter, FastAPI +from fastapi.testclient import TestClient +from pydantic import BaseModel + +app = FastAPI() +router = APIRouter() + + +class Item(BaseModel): + name: str + description: Optional[str] = None + price: float + tax: Optional[float] = None + + +@app.query("/items/") +def query_item(item: Item): + return item + + +@app.query("/items/{item_id}") +def query_item_by_id(item_id: int, item: Item, q: Optional[str] = None): + return {"item_id": item_id, "item": item, "q": q} + + +@router.query("/router-items/") +def router_query_item(item: Item): + return item + + +app.include_router(router) + +client = TestClient(app) + + +def test_query_item(): + response = client.request( + "QUERY", "/items/", json={"name": "Foo", "price": 50.5} + ) + assert response.status_code == 200 + assert response.json() == { + "name": "Foo", + "price": 50.5, + "description": None, + "tax": None, + } + + +def test_query_item_validation_error(): + response = client.request("QUERY", "/items/", json={"name": "Foo"}) + assert response.status_code == 422 + assert response.json() == IsDict( + { + "detail": [ + { + "type": "missing", + "loc": ["body", "price"], + "msg": "Field required", + "input": {"name": "Foo"}, + } + ] + } + ) | IsDict( + # TODO: remove when deprecating Pydantic v1 + { + "detail": [ + { + "loc": ["body", "price"], + "msg": "field required", + "type": "value_error.missing", + } + ] + } + ) + + +def test_query_item_by_id(): + response = client.request( + "QUERY", + "/items/42?q=somequery", + json={"name": "Bar", "price": 10.0}, + ) + assert response.status_code == 200 + assert response.json() == { + "item_id": 42, + "item": {"name": "Bar", "price": 10.0, "description": None, "tax": None}, + "q": "somequery", + } + + +def test_router_query_item(): + response = client.request( + "QUERY", "/router-items/", json={"name": "Baz", "price": 20.0} + ) + assert response.status_code == 200 + assert response.json() == { + "name": "Baz", + "price": 20.0, + "description": None, + "tax": None, + } + + +def test_openapi_schema(): + response = client.get("/openapi.json") + assert response.status_code == 200 + assert response.json() == { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "query": { + "summary": "Query Item", + "operationId": "query_item_items__query", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/items/{item_id}": { + "query": { + "summary": "Query Item By Id", + "operationId": "query_item_by_id_items__item_id__query", + "parameters": [ + { + "required": True, + "schema": {"title": "Item Id", "type": "integer"}, + "name": "item_id", + "in": "path", + }, + { + "required": False, + "schema": IsDict( + { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Q", + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Q", "type": "string"} + ), + "name": "q", + "in": "query", + }, + ], + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + "/router-items/": { + "query": { + "summary": "Router Query Item", + "operationId": "router_query_item_router_items__query", + "requestBody": { + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": {"application/json": {"schema": {}}}, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + }, + }, + "components": { + "schemas": { + "HTTPValidationError": { + "title": "HTTPValidationError", + "type": "object", + "properties": { + "detail": { + "title": "Detail", + "type": "array", + "items": {"$ref": "#/components/schemas/ValidationError"}, + } + }, + }, + "Item": { + "title": "Item", + "required": ["name", "price"], + "type": "object", + "properties": { + "name": {"title": "Name", "type": "string"}, + "description": IsDict( + { + "title": "Description", + "anyOf": [{"type": "string"}, {"type": "null"}], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Description", "type": "string"} + ), + "price": {"title": "Price", "type": "number"}, + "tax": IsDict( + { + "title": "Tax", + "anyOf": [{"type": "number"}, {"type": "null"}], + } + ) + | IsDict( + # TODO: remove when deprecating Pydantic v1 + {"title": "Tax", "type": "number"} + ), + }, + }, + "ValidationError": { + "title": "ValidationError", + "required": ["loc", "msg", "type"], + "type": "object", + "properties": { + "loc": { + "title": "Location", + "type": "array", + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + }, + "msg": {"title": "Message", "type": "string"}, + "type": {"title": "Error Type", "type": "string"}, + }, + }, + } + }, + } \ No newline at end of file