-
-
Notifications
You must be signed in to change notification settings - Fork 206
Expand file tree
/
Copy pathtest_decorator.py
More file actions
194 lines (148 loc) · 7.07 KB
/
Copy pathtest_decorator.py
File metadata and controls
194 lines (148 loc) · 7.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import asyncio
import time
from typing import Any, Dict, Generator, List, Tuple
import pendulum
import pytest
from starlette.testclient import TestClient
from examples.in_memory.main import app
from fastapi_cache import FastAPICache
from fastapi_cache.backends.inmemory import InMemoryBackend
from fastapi_cache.decorator import cache
@pytest.fixture(autouse=True)
def _init_cache() -> Generator[Any, Any, None]: # pyright: ignore[reportUnusedFunction]
FastAPICache.init(InMemoryBackend())
yield
FastAPICache.reset()
def test_datetime() -> None:
with TestClient(app) as client:
response = client.get("/datetime")
assert response.headers.get("X-FastAPI-Cache") == "MISS"
now = response.json().get("now")
now_ = pendulum.now().replace(microsecond=0)
assert pendulum.parse(now).replace(microsecond=0) == now_
response = client.get("/datetime")
assert response.headers.get("X-FastAPI-Cache") == "HIT"
now = response.json().get("now")
assert pendulum.parse(now).replace(microsecond=0) == now_
time.sleep(3)
response = client.get("/datetime")
now = response.json().get("now")
assert response.headers.get("X-FastAPI-Cache") == "MISS"
now = pendulum.parse(now).replace(microsecond=0)
assert now != now_
assert now == pendulum.now().replace(microsecond=0)
def test_date() -> None:
"""Test path function without request or response arguments."""
with TestClient(app) as client:
response = client.get("/date")
assert response.headers.get("X-FastAPI-Cache") == "MISS"
assert pendulum.parse(response.json()) == pendulum.today()
# do it again to test cache
response = client.get("/date")
assert response.headers.get("X-FastAPI-Cache") == "HIT"
assert pendulum.parse(response.json()) == pendulum.today()
# now test with cache disabled, as that's a separate code path
FastAPICache._enable = False # pyright: ignore[reportPrivateUsage]
response = client.get("/date")
assert "X-FastAPI-Cache" not in response.headers
assert pendulum.parse(response.json()) == pendulum.today()
FastAPICache._enable = True # pyright: ignore[reportPrivateUsage]
def test_sync() -> None:
"""Ensure that sync function support works."""
with TestClient(app) as client:
response = client.get("/sync-me")
assert response.json() == 42
def test_cache_response_obj() -> None:
with TestClient(app) as client:
cache_response = client.get("cache_response_obj")
assert cache_response.json() == {"a": 1}
get_cache_response = client.get("cache_response_obj")
assert get_cache_response.json() == {"a": 1}
assert get_cache_response.headers.get("cache-control")
assert get_cache_response.headers.get("etag")
def test_kwargs() -> None:
with TestClient(app) as client:
name = "Jon"
response = client.get("/kwargs", params={"name": name})
assert "X-FastAPI-Cache" not in response.headers
assert response.json() == {"name": name}
def test_method() -> None:
with TestClient(app) as client:
response = client.get("/method")
assert response.json() == 17
def test_pydantic_model() -> None:
with TestClient(app) as client:
r1 = client.get("/pydantic_instance")
assert r1.headers.get("X-FastAPI-Cache") == "MISS"
r2 = client.get("/pydantic_instance")
assert r2.headers.get("X-FastAPI-Cache") == "HIT"
assert r1.json() == r2.json()
def test_non_get() -> None:
with TestClient(app) as client:
response = client.put("/uncached_put")
assert "X-FastAPI-Cache" not in response.headers
assert response.json() == {"value": 1}
response = client.put("/uncached_put")
assert "X-FastAPI-Cache" not in response.headers
assert response.json() == {"value": 2}
def test_alternate_injected_namespace() -> None:
with TestClient(app) as client:
response = client.get("/namespaced_injection")
assert response.headers.get("X-FastAPI-Cache") == "MISS"
assert response.json() == {"__fastapi_cache_request": 42, "__fastapi_cache_response": 17}
def test_cache_control() -> None:
with TestClient(app) as client:
response = client.get("/cached_put")
assert response.json() == {"value": 1}
# HIT
response = client.get("/cached_put")
assert response.json() == {"value": 1}
# no-cache
response = client.get("/cached_put", headers={"Cache-Control": "no-cache"})
assert response.json() == {"value": 2}
response = client.get("/cached_put")
assert response.json() == {"value": 2}
# no-store
response = client.get("/cached_put", headers={"Cache-Control": "no-store"})
assert response.json() == {"value": 3}
response = client.get("/cached_put")
assert response.json() == {"value": 2}
def test_exclude_params() -> None:
"""Parameters listed in exclude_params are left out of the cache key."""
with TestClient(app) as client:
response = client.get("/excluded_params", params={"name": "Jon", "nonce": "a"})
assert response.headers.get("X-FastAPI-Cache") == "MISS"
assert response.json() == {"name": "Jon", "nonce": "a", "value": 1}
# a different nonce hits the same cache entry
response = client.get("/excluded_params", params={"name": "Jon", "nonce": "b"})
assert response.headers.get("X-FastAPI-Cache") == "HIT"
assert response.json() == {"name": "Jon", "nonce": "a", "value": 1}
# a different name is still a distinct entry
response = client.get("/excluded_params", params={"name": "Ben", "nonce": "b"})
assert response.headers.get("X-FastAPI-Cache") == "MISS"
assert response.json() == {"name": "Ben", "nonce": "b", "value": 2}
def test_exclude_params_positional() -> None:
"""Positional arguments are matched to their parameter name by position."""
calls: List[Tuple[int, int]] = []
@cache(namespace="test", expire=5, exclude_params=["b"])
async def func(a: int, b: int) -> int:
calls.append((a, b))
return a
assert asyncio.run(func(1, 2)) == 1
assert asyncio.run(func(1, 3)) == 1
assert calls == [(1, 2)]
assert asyncio.run(func(4, 3)) == 4
assert calls == [(1, 2), (4, 3)]
def test_exclude_params_unknown_name() -> None:
"""A typo in exclude_params is reported when the function is decorated."""
with pytest.raises(ValueError, match="nonexistent"):
@cache(namespace="test", exclude_params=["nonexistent"])
async def func(a: int) -> int:
return a
def test_exclude_params_var_keyword() -> None:
"""Functions taking **kwargs accept any excluded name."""
@cache(namespace="test", expire=5, exclude_params=["nonce"])
async def func(**kwargs: Any) -> Dict[str, Any]:
return kwargs
assert asyncio.run(func(name="Jon", nonce="a")) == {"name": "Jon", "nonce": "a"}
assert asyncio.run(func(name="Jon", nonce="b")) == {"name": "Jon", "nonce": "a"}