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

120 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-04-06 01:24 +0000

1import importlib 1abcd

2from types import ModuleType 1abcd

3 

4import pytest 1abcd

5from fastapi.testclient import TestClient 1abcd

6from inline_snapshot import snapshot 1abcd

7 

8from ...utils import needs_py310 1abcd

9 

10 

11@pytest.fixture( 1abcd

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): 1abcd

19 mod = importlib.import_module(f"docs_src.security.{request.param}") 2KbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*b+b,b-b.b/b:b;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccdcecfcgchcicjckclcmcncocpcqcrcsctcucvcwcxcyczcAcBcCcDcEcFcGcHcIcJcKcLcMcNcOcPcQcRcScTcUcVcWcXcYcZc0c1c2c3c4c5c6c7c8c9c!c

20 

21 return mod 2KbLbMbNbObPbQbRbSbTbUbVbWbXbYbZb0b1b2b3b4b5b6b7b8b9b!b#b$b%b'b(b)b*b+b,b-b.b/b:b;b=b?b@b[b]b^b_b`b{b|b}b~bacbcccdcecfcgchcicjckclcmcncocpcqcrcsctcucvcwcxcyczcAcBcCcDcEcFcGcHcIcJcKcLcMcNcOcPcQcRcScTcUcVcWcXcYcZc0c1c2c3c4c5c6c7c8c9c!c

22 

23 

24def get_access_token( 1abcd

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

26): 

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

28 if scope: 1efEFghijklmnopGHqrstuvwxIJyzABCD

29 data["scope"] = scope 1efghijmnopqrstwxyzAB

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

31 content = response.json() 1efEFghijklmnopGHqrstuvwxIJyzABCD

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

33 return access_token 1efEFghijklmnopGHqrstuvwxIJyzABCD

34 

35 

36def test_login(mod: ModuleType): 1abcd

37 client = TestClient(mod.app) 1KLMNOPQR

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

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

40 content = response.json() 1KLMNOPQR

41 assert "access_token" in content 1KLMNOPQR

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

43 

44 

45def test_login_incorrect_password(mod: ModuleType): 1abcd

46 client = TestClient(mod.app) 2nbobpbqbrbsbtb

47 response = client.post( 2nbobpbqbrbsbtb

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

49 ) 

50 assert response.status_code == 400, response.text 2nbobpbqbrbsbtb

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

52 

53 

54def test_login_incorrect_username(mod: ModuleType): 1abcd

55 client = TestClient(mod.app) 2ubvbwbxbybzbAbBb

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

57 assert response.status_code == 400, response.text 2ubvbwbxbybzbAbBb

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

59 

60 

61def test_no_token(mod: ModuleType): 1abcd

62 client = TestClient(mod.app) 1TUVWXYZ

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

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

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

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

67 

68 

69def test_token(mod: ModuleType): 1abcd

70 client = TestClient(mod.app) 1ghmnqryz

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

72 response = client.get( 1ghmnqryz

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

74 ) 

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

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

77 "username": "johndoe", 

78 "full_name": "John Doe", 

79 "email": "[email protected]", 

80 "disabled": False, 

81 } 

82 

83 

84def test_incorrect_token(mod: ModuleType): 1abcd

85 client = TestClient(mod.app) 101234567

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

87 assert response.status_code == 401, response.text 101234567

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

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

90 

91 

92def test_incorrect_token_type(mod: ModuleType): 1abcd

93 client = TestClient(mod.app) 189!#$%'

94 response = client.get( 189!#$%'

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

96 ) 

97 assert response.status_code == 401, response.text 189!#$%'

98 assert response.json() == {"detail": "Not authenticated"} 189!#$%'

99 assert response.headers["WWW-Authenticate"] == "Bearer" 189!#$%'

100 

101 

102def test_verify_password(mod: ModuleType): 1abcd

103 assert mod.verify_password( 2,c-c.c/c:c;c=c?c

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

105 ) 

106 

107 

108def test_get_password_hash(mod: ModuleType): 1abcd

109 assert mod.get_password_hash("secretalice") 2@c[c]c^c_c`c{c|c

110 

111 

112def test_create_access_token(mod: ModuleType): 1abcd

113 access_token = mod.create_access_token(data={"data": "foo"}) 2#c$c%c'c(c)c*c+c

114 assert access_token 2#c$c%c'c(c)c*c+c

115 

116 

117def test_token_no_sub(mod: ModuleType): 1abcd

118 client = TestClient(mod.app) 1()*+,-.

119 

120 response = client.get( 1()*+,-.

121 "/users/me", 

122 headers={ 

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

124 }, 

125 ) 

126 assert response.status_code == 401, response.text 1()*+,-.

127 assert response.json() == {"detail": "Could not validate credentials"} 1()*+,-.

128 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1()*+,-.

129 

130 

131def test_token_no_username(mod: ModuleType): 1abcd

132 client = TestClient(mod.app) 1/:;=?@[]

133 

134 response = client.get( 1/:;=?@[]

135 "/users/me", 

136 headers={ 

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

138 }, 

139 ) 

140 assert response.status_code == 401, response.text 1/:;=?@[]

141 assert response.json() == {"detail": "Could not validate credentials"} 1/:;=?@[]

142 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1/:;=?@[]

143 

144 

145def test_token_no_scope(mod: ModuleType): 1abcd

146 client = TestClient(mod.app) 1klSuvCD

147 

148 access_token = get_access_token(client=client) 1klSuvCD

149 response = client.get( 1klSuvCD

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

151 ) 

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

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

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

155 

156 

157def test_token_nonexistent_user(mod: ModuleType): 1abcd

158 client = TestClient(mod.app) 1^_`{|}~

159 

160 response = client.get( 1^_`{|}~

161 "/users/me", 

162 headers={ 

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

164 }, 

165 ) 

166 assert response.status_code == 401, response.text 1^_`{|}~

167 assert response.json() == {"detail": "Could not validate credentials"} 1^_`{|}~

168 assert response.headers["WWW-Authenticate"] == 'Bearer scope="me"' 1^_`{|}~

169 

170 

171def test_token_inactive_user(mod: ModuleType): 1abcd

172 client = TestClient(mod.app) 2i j abs t A B

173 

174 access_token = get_access_token( 2i j abs t A B

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

176 ) 

177 response = client.get( 2i j abs t A B

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

179 ) 

180 assert response.status_code == 400, response.text 2i j abs t A B

181 assert response.json() == {"detail": "Inactive user"} 2i j abs t A B

182 

183 

184def test_read_items(mod: ModuleType): 1abcd

185 client = TestClient(mod.app) 2e f bbcbo p w x

186 access_token = get_access_token(scope="me items", client=client) 2e f bbcbo p w x

187 response = client.get( 2e f bbcbo p w x

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

189 ) 

190 assert response.status_code == 200, response.text 2e f bbcbo p w x

191 assert response.json() == [{"item_id": "Foo", "owner": "johndoe"}] 2e f bbcbo p w x

192 

193 

194def test_read_system_status(mod: ModuleType): 1abcd

195 client = TestClient(mod.app) 2E F dbebG H I J

196 access_token = get_access_token(client=client) 2E F dbebG H I J

197 response = client.get( 2E F dbebG H I J

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

199 ) 

200 assert response.status_code == 200, response.text 2E F dbebG H I J

201 assert response.json() == {"status": "ok"} 2E F dbebG H I J

202 

203 

204def test_read_system_status_no_token(mod: ModuleType): 1abcd

205 client = TestClient(mod.app) 2fbgbhbibjbkblbmb

206 response = client.get("/status/") 2fbgbhbibjbkblbmb

207 assert response.status_code == 401, response.text 2fbgbhbibjbkblbmb

208 assert response.json() == {"detail": "Not authenticated"} 2fbgbhbibjbkblbmb

209 assert response.headers["WWW-Authenticate"] == "Bearer" 2fbgbhbibjbkblbmb

210 

211 

212def test_openapi_schema(mod: ModuleType): 1abcd

213 client = TestClient(mod.app) 2CbDbEbFbGbHbIbJb

214 response = client.get("/openapi.json") 2CbDbEbFbGbHbIbJb

215 assert response.status_code == 200, response.text 2CbDbEbFbGbHbIbJb

216 assert response.json() == snapshot( 2CbDbEbFbGbHbIbJb

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 )