Coverage for tests / test_tutorial / test_security / test_tutorial005.py: 100%

120 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-12 18:15 +0000

1import importlib 1abdc

2from types import ModuleType 1abdc

3 

4import pytest 1abdc

5from fastapi.testclient import TestClient 1abdc

6from inline_snapshot import snapshot 1abdc

7 

8from ...utils import needs_py310 1abdc

9 

10 

11@pytest.fixture( 1abdc

12 name="mod", 

13 params=[ 

14 pytest.param("tutorial005_py310", marks=needs_py310), 

15 pytest.param("tutorial005_an_py310", marks=needs_py310), 

16 ], 

17) 

18def get_mod(request: pytest.FixtureRequest): 1abdc

19 mod = importlib.import_module(f"docs_src.security.{request.param}") 1abc

20 

21 return mod 1abc

22 

23 

24def get_access_token( 1abdc

25 *, username="johndoe", password="secret", scope=None, client: TestClient 

26): 

27 data = {"username": username, "password": password} 1eqfghirjklmsnop

28 if scope: 1eqfghirjklmsnop

29 data["scope"] = scope 1efgijkmno

30 response = client.post("/token", data=data) 1eqfghirjklmsnop

31 content = response.json() 1eqfghirjklmsnop

32 access_token = content.get("access_token") 1eqfghirjklmsnop

33 return access_token 1eqfghirjklmsnop

34 

35 

36def test_login(mod: ModuleType): 1abdc

37 client = TestClient(mod.app) 1tuv

38 response = client.post("/token", data={"username": "johndoe", "password": "secret"}) 1tuv

39 assert response.status_code == 200, response.text 1tuv

40 content = response.json() 1tuv

41 assert "access_token" in content 1tuv

42 assert content["token_type"] == "bearer" 1tuv

43 

44 

45def test_login_incorrect_password(mod: ModuleType): 1abdc

46 client = TestClient(mod.app) 1RST

47 response = client.post( 1RST

48 "/token", data={"username": "johndoe", "password": "incorrect"} 

49 ) 

50 assert response.status_code == 400, response.text 1RST

51 assert response.json() == {"detail": "Incorrect username or password"} 1RST

52 

53 

54def test_login_incorrect_username(mod: ModuleType): 1abdc

55 client = TestClient(mod.app) 1UVW

56 response = client.post("/token", data={"username": "foo", "password": "secret"}) 1UVW

57 assert response.status_code == 400, response.text 1UVW

58 assert response.json() == {"detail": "Incorrect username or password"} 1UVW

59 

60 

61def test_no_token(mod: ModuleType): 1abdc

62 client = TestClient(mod.app) 1wxy

63 response = client.get("/users/me") 1wxy

64 assert response.status_code == 401, response.text 1wxy

65 assert response.json() == {"detail": "Not authenticated"} 1wxy

66 assert response.headers["WWW-Authenticate"] == "Bearer" 1wxy

67 

68 

69def test_token(mod: ModuleType): 1abdc

70 client = TestClient(mod.app) 1fjn

71 access_token = get_access_token(scope="me", client=client) 1fjn

72 response = client.get( 1fjn

73 "/users/me", headers={"Authorization": f"Bearer {access_token}"} 

74 ) 

75 assert response.status_code == 200, response.text 1fjn

76 assert response.json() == { 1fjn

77 "username": "johndoe", 

78 "full_name": "John Doe", 

79 "email": "johndoe@example.com", 

80 "disabled": False, 

81 } 

82 

83 

84def test_incorrect_token(mod: ModuleType): 1abdc

85 client = TestClient(mod.app) 1zAB

86 response = client.get("/users/me", headers={"Authorization": "Bearer nonexistent"}) 1zAB

87 assert response.status_code == 401, response.text 1zAB

88 assert response.json() == {"detail": "Could not validate credentials"} 1zAB

89 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1zAB

90 

91 

92def test_incorrect_token_type(mod: ModuleType): 1abdc

93 client = TestClient(mod.app) 1CDE

94 response = client.get( 1CDE

95 "/users/me", headers={"Authorization": "Notexistent testtoken"} 

96 ) 

97 assert response.status_code == 401, response.text 1CDE

98 assert response.json() == {"detail": "Not authenticated"} 1CDE

99 assert response.headers["WWW-Authenticate"] == "Bearer" 1CDE

100 

101 

102def test_verify_password(mod: ModuleType): 1abdc

103 assert mod.verify_password( 1345

104 "secret", mod.fake_users_db["johndoe"]["hashed_password"] 

105 ) 

106 

107 

108def test_get_password_hash(mod: ModuleType): 1abdc

109 assert mod.get_password_hash("secretalice") 1678

110 

111 

112def test_create_access_token(mod: ModuleType): 1abdc

113 access_token = mod.create_access_token(data={"data": "foo"}) 1012

114 assert access_token 1012

115 

116 

117def test_token_no_sub(mod: ModuleType): 1abdc

118 client = TestClient(mod.app) 1FGH

119 

120 response = client.get( 1FGH

121 "/users/me", 

122 headers={ 

123 "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiZm9vIn0.9ynBhuYb4e6aW3oJr_K_TBgwcMTDpRToQIE25L57rOE" 

124 }, 

125 ) 

126 assert response.status_code == 401, response.text 1FGH

127 assert response.json() == {"detail": "Could not validate credentials"} 1FGH

128 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1FGH

129 

130 

131def test_token_no_username(mod: ModuleType): 1abdc

132 client = TestClient(mod.app) 1IJK

133 

134 response = client.get( 1IJK

135 "/users/me", 

136 headers={ 

137 "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmb28ifQ.NnExK_dlNAYyzACrXtXDrcWOgGY2JuPbI4eDaHdfK5Y" 

138 }, 

139 ) 

140 assert response.status_code == 401, response.text 1IJK

141 assert response.json() == {"detail": "Could not validate credentials"} 1IJK

142 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1IJK

143 

144 

145def test_token_no_scope(mod: ModuleType): 1abdc

146 client = TestClient(mod.app) 1hlp

147 

148 access_token = get_access_token(client=client) 1hlp

149 response = client.get( 1hlp

150 "/users/me", headers={"Authorization": f"Bearer {access_token}"} 

151 ) 

152 assert response.status_code == 401, response.text 1hlp

153 assert response.json() == {"detail": "Not enough permissions"} 1hlp

154 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1hlp

155 

156 

157def test_token_nonexistent_user(mod: ModuleType): 1abdc

158 client = TestClient(mod.app) 1LMN

159 

160 response = client.get( 1LMN

161 "/users/me", 

162 headers={ 

163 "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VybmFtZTpib2IifQ.HcfCW67Uda-0gz54ZWTqmtgJnZeNem0Q757eTa9EZuw" 

164 }, 

165 ) 

166 assert response.status_code == 401, response.text 1LMN

167 assert response.json() == {"detail": "Could not validate credentials"} 1LMN

168 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1LMN

169 

170 

171def test_token_inactive_user(mod: ModuleType): 1abdc

172 client = TestClient(mod.app) 1gko

173 

174 access_token = get_access_token( 1gko

175 username="alice", password="secretalice", scope="me", client=client 

176 ) 

177 response = client.get( 1gko

178 "/users/me", headers={"Authorization": f"Bearer {access_token}"} 

179 ) 

180 assert response.status_code == 400, response.text 1gko

181 assert response.json() == {"detail": "Inactive user"} 1gko

182 

183 

184def test_read_items(mod: ModuleType): 1abdc

185 client = TestClient(mod.app) 1eim

186 access_token = get_access_token(scope="me items", client=client) 1eim

187 response = client.get( 1eim

188 "/users/me/items/", headers={"Authorization": f"Bearer {access_token}"} 

189 ) 

190 assert response.status_code == 200, response.text 1eim

191 assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}] 1eim

192 

193 

194def test_read_system_status(mod: ModuleType): 1abdc

195 client = TestClient(mod.app) 1qrs

196 access_token = get_access_token(client=client) 1qrs

197 response = client.get( 1qrs

198 "/status/", headers={"Authorization": f"Bearer {access_token}"} 

199 ) 

200 assert response.status_code == 200, response.text 1qrs

201 assert response.json() == {"status": "ok"} 1qrs

202 

203 

204def test_read_system_status_no_token(mod: ModuleType): 1abdc

205 client = TestClient(mod.app) 1OPQ

206 response = client.get("/status/") 1OPQ

207 assert response.status_code == 401, response.text 1OPQ

208 assert response.json() == {"detail": "Not authenticated"} 1OPQ

209 assert response.headers["WWW-Authenticate"] == "Bearer" 1OPQ

210 

211 

212def test_openapi_schema(mod: ModuleType): 1abdc

213 client = TestClient(mod.app) 1XYZ

214 response = client.get("/openapi.json") 1XYZ

215 assert response.status_code == 200, response.text 1XYZ

216 assert response.json() == snapshot( 1XYZ

217 { 

218 "openapi": "3.1.0", 

219 "info": {"title": "FastAPI", "version": "0.1.0"}, 

220 "paths": { 

221 "/token": { 

222 "post": { 

223 "responses": { 

224 "200": { 

225 "description": "Successful Response", 

226 "content": { 

227 "application/json": { 

228 "schema": {"$ref": "#/components/schemas/Token"} 

229 } 

230 }, 

231 }, 

232 "422": { 

233 "description": "Validation Error", 

234 "content": { 

235 "application/json": { 

236 "schema": { 

237 "$ref": "#/components/schemas/HTTPValidationError" 

238 } 

239 } 

240 }, 

241 }, 

242 }, 

243 "summary": "Login For Access Token", 

244 "operationId": "login_for_access_token_token_post", 

245 "requestBody": { 

246 "content": { 

247 "application/x-www-form-urlencoded": { 

248 "schema": { 

249 "$ref": "#/components/schemas/Body_login_for_access_token_token_post" 

250 } 

251 } 

252 }, 

253 "required": True, 

254 }, 

255 } 

256 }, 

257 "/users/me/": { 

258 "get": { 

259 "responses": { 

260 "200": { 

261 "description": "Successful Response", 

262 "content": { 

263 "application/json": { 

264 "schema": {"$ref": "#/components/schemas/User"} 

265 } 

266 }, 

267 } 

268 }, 

269 "summary": "Read Users Me", 

270 "operationId": "read_users_me_users_me__get", 

271 "security": [{"OAuth2PasswordBearer": ["me"]}], 

272 } 

273 }, 

274 "/users/me/items/": { 

275 "get": { 

276 "responses": { 

277 "200": { 

278 "description": "Successful Response", 

279 "content": {"application/json": {"schema": {}}}, 

280 } 

281 }, 

282 "summary": "Read Own Items", 

283 "operationId": "read_own_items_users_me_items__get", 

284 "security": [{"OAuth2PasswordBearer": ["items", "me"]}], 

285 } 

286 }, 

287 "/status/": { 

288 "get": { 

289 "responses": { 

290 "200": { 

291 "description": "Successful Response", 

292 "content": {"application/json": {"schema": {}}}, 

293 } 

294 }, 

295 "summary": "Read System Status", 

296 "operationId": "read_system_status_status__get", 

297 "security": [{"OAuth2PasswordBearer": []}], 

298 } 

299 }, 

300 }, 

301 "components": { 

302 "schemas": { 

303 "User": { 

304 "title": "User", 

305 "required": ["username"], 

306 "type": "object", 

307 "properties": { 

308 "username": {"title": "Username", "type": "string"}, 

309 "email": { 

310 "title": "Email", 

311 "anyOf": [{"type": "string"}, {"type": "null"}], 

312 }, 

313 "full_name": { 

314 "title": "Full Name", 

315 "anyOf": [{"type": "string"}, {"type": "null"}], 

316 }, 

317 "disabled": { 

318 "title": "Disabled", 

319 "anyOf": [{"type": "boolean"}, {"type": "null"}], 

320 }, 

321 }, 

322 }, 

323 "Token": { 

324 "title": "Token", 

325 "required": ["access_token", "token_type"], 

326 "type": "object", 

327 "properties": { 

328 "access_token": {"title": "Access Token", "type": "string"}, 

329 "token_type": {"title": "Token Type", "type": "string"}, 

330 }, 

331 }, 

332 "Body_login_for_access_token_token_post": { 

333 "title": "Body_login_for_access_token_token_post", 

334 "required": ["username", "password"], 

335 "type": "object", 

336 "properties": { 

337 "grant_type": { 

338 "title": "Grant Type", 

339 "anyOf": [ 

340 {"pattern": "^password$", "type": "string"}, 

341 {"type": "null"}, 

342 ], 

343 }, 

344 "username": {"title": "Username", "type": "string"}, 

345 "password": { 

346 "title": "Password", 

347 "type": "string", 

348 "format": "password", 

349 }, 

350 "scope": { 

351 "title": "Scope", 

352 "type": "string", 

353 "default": "", 

354 }, 

355 "client_id": { 

356 "title": "Client Id", 

357 "anyOf": [{"type": "string"}, {"type": "null"}], 

358 }, 

359 "client_secret": { 

360 "title": "Client Secret", 

361 "anyOf": [{"type": "string"}, {"type": "null"}], 

362 "format": "password", 

363 }, 

364 }, 

365 }, 

366 "ValidationError": { 

367 "title": "ValidationError", 

368 "required": ["loc", "msg", "type"], 

369 "type": "object", 

370 "properties": { 

371 "loc": { 

372 "title": "Location", 

373 "type": "array", 

374 "items": { 

375 "anyOf": [{"type": "string"}, {"type": "integer"}] 

376 }, 

377 }, 

378 "msg": {"title": "Message", "type": "string"}, 

379 "type": {"title": "Error Type", "type": "string"}, 

380 "input": {"title": "Input"}, 

381 "ctx": {"title": "Context", "type": "object"}, 

382 }, 

383 }, 

384 "HTTPValidationError": { 

385 "title": "HTTPValidationError", 

386 "type": "object", 

387 "properties": { 

388 "detail": { 

389 "title": "Detail", 

390 "type": "array", 

391 "items": { 

392 "$ref": "#/components/schemas/ValidationError" 

393 }, 

394 } 

395 }, 

396 }, 

397 }, 

398 "securitySchemes": { 

399 "OAuth2PasswordBearer": { 

400 "type": "oauth2", 

401 "flows": { 

402 "password": { 

403 "scopes": { 

404 "me": "Read information about the current user.", 

405 "items": "Read items.", 

406 }, 

407 "tokenUrl": "token", 

408 } 

409 }, 

410 } 

411 }, 

412 }, 

413 } 

414 )