Files
fastapi/tests/test_security_websockets.py
T
1884325cd6 ✨ Add WebSocket support to security dependencies (#10147)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2026-10-18 13:09:15 +02:00

162 lines
4.7 KiB
Python

from functools import partial
import pytest
from fastapi import FastAPI, Security, WebSocket
from fastapi.encoders import jsonable_encoder
from fastapi.security import (
APIKeyCookie,
APIKeyHeader,
APIKeyQuery,
HTTPBasic,
HTTPBearer,
HTTPDigest,
OAuth2,
OAuth2AuthorizationCodeBearer,
OAuth2PasswordBearer,
OpenIdConnect,
)
from fastapi.security.http import HTTPBase
from fastapi.testclient import TestClient
from starlette.testclient import WebSocketDenialResponse
@pytest.mark.parametrize(
"security_factory, path, headers, expected, challenge",
[
pytest.param(
partial(HTTPBase, scheme="Other"),
"/",
{"Authorization": "Other secret"},
{"scheme": "Other", "credentials": "secret"},
"Other",
id="http-base",
),
pytest.param(
HTTPBasic,
"/",
{"Authorization": "Basic am9objpzZWNyZXQ="},
{"username": "john", "password": "secret"},
"Basic",
id="http-basic",
),
pytest.param(
HTTPBearer,
"/",
{"Authorization": "Bearer secret"},
{"scheme": "Bearer", "credentials": "secret"},
"Bearer",
id="http-bearer",
),
pytest.param(
HTTPDigest,
"/",
{"Authorization": "Digest secret"},
{"scheme": "Digest", "credentials": "secret"},
"Digest",
id="http-digest",
),
pytest.param(
partial(APIKeyQuery, name="key"),
"/?key=secret",
{},
"secret",
"APIKey",
id="api-key-query",
),
pytest.param(
partial(APIKeyHeader, name="key"),
"/",
{"key": "secret"},
"secret",
"APIKey",
id="api-key-header",
),
pytest.param(
partial(APIKeyCookie, name="key"),
"/",
{"Cookie": "key=secret"},
"secret",
"APIKey",
id="api-key-cookie",
),
pytest.param(
OAuth2,
"/",
{"Authorization": "Bearer secret"},
"Bearer secret",
"Bearer",
id="oauth2",
),
pytest.param(
partial(OAuth2PasswordBearer, tokenUrl="/token"),
"/",
{"Authorization": "Bearer secret"},
"secret",
"Bearer",
id="oauth2-password-bearer",
),
pytest.param(
partial(
OAuth2AuthorizationCodeBearer,
authorizationUrl="/authorize",
tokenUrl="/token",
),
"/",
{"Authorization": "Bearer secret"},
"secret",
"Bearer",
id="oauth2-authorization-code-bearer",
),
pytest.param(
partial(OpenIdConnect, openIdConnectUrl="/openid"),
"/",
{"Authorization": "Bearer secret"},
"Bearer secret",
"Bearer",
id="openid-connect",
),
],
)
@pytest.mark.parametrize(
"auto_error, has_credentials",
[
pytest.param(True, True, id="valid-credentials"),
pytest.param(True, False, id="missing-required-credentials"),
pytest.param(False, False, id="missing-optional-credentials"),
],
)
def test_security_websocket(
security_factory,
path: str,
headers: dict[str, str],
expected: dict[str, str] | str,
challenge: str,
auto_error: bool,
has_credentials: bool,
):
security = security_factory(auto_error=auto_error)
app = FastAPI()
@app.websocket("/")
async def endpoint(websocket: WebSocket, credentials=Security(security)):
await websocket.accept()
await websocket.send_json(jsonable_encoder(credentials))
await websocket.close()
with TestClient(app) as client:
if auto_error and not has_credentials:
with pytest.raises(WebSocketDenialResponse) as exc_info:
with client.websocket_connect("/"):
pass # pragma: no cover
assert exc_info.value.status_code == 401
assert exc_info.value.json() == {"detail": "Not authenticated"}
assert exc_info.value.headers["WWW-Authenticate"] == challenge
else:
with client.websocket_connect(
path if has_credentials else "/",
headers=headers if has_credentials else {},
) as websocket:
assert websocket.receive_json() == (
expected if has_credentials else None
)