starlette/tests/test_database.py

169 lines
5.1 KiB
Python
Raw Normal View History

import databases
import pytest
import sqlalchemy
from starlette.applications import Starlette
from starlette.responses import JSONResponse
DATABASE_URL = "sqlite:///test.db"
metadata = sqlalchemy.MetaData()
notes = sqlalchemy.Table(
"notes",
metadata,
sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
sqlalchemy.Column("text", sqlalchemy.String(length=100)),
sqlalchemy.Column("completed", sqlalchemy.Boolean),
)
anyio integration (#1157) * First whack at anyio integration * Fix formatting * Remove debug messages * mypy fixes * Update README.md Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com> * Fix install_requires typo * move_on_after blocks if deadline is too small * Linter fixes * Improve WSGI structured concurrency * Tests use anyio * Checkin progress on testclient * Prep for anyio 3 * Remove debug backend option * Use anyio 3.0.0rc1 * Remove old style executor from GraphQLApp * Fix extra import * Don't cancel task scope early * Wait for wsgi sender to finish before exiting * Use memory object streams in websocket tests * Test on asyncio, asyncio+uvloop, and trio * Formatting fixes * run_until_first_complete doesn't need a return * Fix middleware app call * Simplify middleware exceptions * Use anyio for websocket test * Set STARLETTE_TESTCLIENT_ASYNC_BACKEND in tests * Pass async backend to portal * Formatting fixes * Bump anyio * Cleanup portals and add TestClient.async_backend * Use anyio.run_async_from_thread to send from worker thread * Use websocket_connect as context manager * Document changes in TestClient * Formatting fix * Fix websocket raises coverage * Update to anyio 3.0.0rc3 and replace aiofiles * Apply suggestions from code review Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi> * Bump to require anyio 3.0.0 final * Remove mention of aiofiles in README.md * Pin jinja2 to releases before 3 due to DeprecationWarnings * Add task_group as application attribute * Remove run_until_first_complete * Undo jinja pin * Refactor anyio.sleep into an event * Use one less task in test_websocket_concurrency_pattern * Apply review suggestions * Rename argument * fix start_task_soon type * fix BaseHTTPMiddleware when used without Starlette * Testclient receive() is a non-trapping function if the response is already complete This allows for a zero deadline when waiting for a disconnect message * Use variable annotation for async_backend * Update docs regarding dependency on anyio * Use CancelScope instead of move_on_after in request.is_disconnected * Cancel task group after returning middleware response Add test for https://github.com/encode/starlette/issues/1022 * Add link to anyio backend options in testclient docs * Add types-dataclasses * Re-implement starlette.concurrency.run_until_first_complete and add a test * Fix type on handler callable * Apply review comments to clarify run_until_first_complete scope Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com> Co-authored-by: Alex Grönholm <alex.gronholm@nextday.fi> Co-authored-by: Thomas Grainger <tagrain@gmail.com>
2021-06-18 14:48:43 +00:00
pytestmark = pytest.mark.usefixtures("no_trio_support")
@pytest.fixture(autouse=True, scope="module")
def create_test_database():
engine = sqlalchemy.create_engine(DATABASE_URL)
metadata.create_all(engine)
yield
metadata.drop_all(engine)
app = Starlette()
database = databases.Database(DATABASE_URL, force_rollback=True)
@app.on_event("startup")
async def startup():
await database.connect()
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
@app.route("/notes", methods=["GET"])
async def list_notes(request):
query = notes.select()
results = await database.fetch_all(query)
content = [
{"text": result["text"], "completed": result["completed"]} for result in results
]
return JSONResponse(content)
@app.route("/notes", methods=["POST"])
@database.transaction()
async def add_note(request):
data = await request.json()
query = notes.insert().values(text=data["text"], completed=data["completed"])
await database.execute(query)
if "raise_exc" in request.query_params:
raise RuntimeError()
return JSONResponse({"text": data["text"], "completed": data["completed"]})
@app.route("/notes/bulk_create", methods=["POST"])
async def bulk_create_notes(request):
data = await request.json()
query = notes.insert()
await database.execute_many(query, data)
return JSONResponse({"notes": data})
@app.route("/notes/{note_id:int}", methods=["GET"])
async def read_note(request):
note_id = request.path_params["note_id"]
query = notes.select().where(notes.c.id == note_id)
result = await database.fetch_one(query)
content = {"text": result["text"], "completed": result["completed"]}
return JSONResponse(content)
@app.route("/notes/{note_id:int}/text", methods=["GET"])
async def read_note_text(request):
note_id = request.path_params["note_id"]
query = sqlalchemy.select([notes.c.text]).where(notes.c.id == note_id)
result = await database.fetch_one(query)
return JSONResponse(result[0])
def test_database(test_client_factory):
with test_client_factory(app) as client:
response = client.post(
"/notes", json={"text": "buy the milk", "completed": True}
)
assert response.status_code == 200
with pytest.raises(RuntimeError):
response = client.post(
"/notes",
json={"text": "you wont see me", "completed": False},
params={"raise_exc": "true"},
)
response = client.post(
"/notes", json={"text": "walk the dog", "completed": False}
)
assert response.status_code == 200
response = client.get("/notes")
assert response.status_code == 200
assert response.json() == [
{"text": "buy the milk", "completed": True},
{"text": "walk the dog", "completed": False},
]
response = client.get("/notes/1")
assert response.status_code == 200
assert response.json() == {"text": "buy the milk", "completed": True}
response = client.get("/notes/1/text")
assert response.status_code == 200
assert response.json() == "buy the milk"
def test_database_execute_many(test_client_factory):
with test_client_factory(app) as client:
response = client.get("/notes")
2018-12-10 14:37:30 +00:00
data = [
{"text": "buy the milk", "completed": True},
{"text": "walk the dog", "completed": False},
]
response = client.post("/notes/bulk_create", json=data)
assert response.status_code == 200
response = client.get("/notes")
assert response.status_code == 200
assert response.json() == [
{"text": "buy the milk", "completed": True},
{"text": "walk the dog", "completed": False},
]
def test_database_isolated_during_test_cases(test_client_factory):
"""
Using `TestClient` as a context manager
"""
with test_client_factory(app) as client:
response = client.post(
"/notes", json={"text": "just one note", "completed": True}
)
assert response.status_code == 200
response = client.get("/notes")
assert response.status_code == 200
assert response.json() == [{"text": "just one note", "completed": True}]
with test_client_factory(app) as client:
response = client.post(
"/notes", json={"text": "just one note", "completed": True}
)
assert response.status_code == 200
response = client.get("/notes")
assert response.status_code == 200
assert response.json() == [{"text": "just one note", "completed": True}]