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

120 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-05-05 00:03 +0000

1import importlib 1abcdef

2from types import ModuleType 1abcdef

3 

4import pytest 1abcdef

5from dirty_equals import IsDict, IsOneOf 1abcdef

6from fastapi.testclient import TestClient 1abcdef

7 

8from ...utils import needs_py39, needs_py310 1abcdef

9 

10 

11@pytest.fixture( 1abcdef

12 name="mod", 

13 params=[ 

14 "tutorial005", 

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

16 "tutorial005_an", 

17 pytest.param("tutorial005_py39", marks=needs_py39), 

18 pytest.param("tutorial005_an_py39", marks=needs_py39), 

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

20 ], 

21) 

22def get_mod(request: pytest.FixtureRequest): 1abcdef

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

24 

25 return mod 1abcdef

26 

27 

28def get_access_token( 1abcdef

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

30): 

31 data = {"username": username, "password": password} 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD

32 if scope: 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD

33 data["scope"] = scope 1ghiklmopqstuwxyABC

34 response = client.post("/token", data=data) 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD

35 content = response.json() 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD

36 access_token = content.get("access_token") 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD

37 return access_token 1gEhijkFlmnoGpqrsHtuvwIxyzAJBCD

38 

39 

40def test_login(mod: ModuleType): 1abcdef

41 client = TestClient(mod.app) 1KLMNOP

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

43 assert response.status_code == 200, response.text 1KLMNOP

44 content = response.json() 1KLMNOP

45 assert "access_token" in content 1KLMNOP

46 assert content["token_type"] == "bearer" 1KLMNOP

47 

48 

49def test_login_incorrect_password(mod: ModuleType): 1abcdef

50 client = TestClient(mod.app) 2` { | } ~ ab

51 response = client.post( 2` { | } ~ ab

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

53 ) 

54 assert response.status_code == 400, response.text 2` { | } ~ ab

55 assert response.json() == {"detail": "Incorrect username or password"} 2` { | } ~ ab

56 

57 

58def test_login_incorrect_username(mod: ModuleType): 1abcdef

59 client = TestClient(mod.app) 2bbcbdbebfbgb

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

61 assert response.status_code == 400, response.text 2bbcbdbebfbgb

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

63 

64 

65def test_no_token(mod: ModuleType): 1abcdef

66 client = TestClient(mod.app) 1QRSTUV

67 response = client.get("/users/me") 1QRSTUV

68 assert response.status_code == 401, response.text 1QRSTUV

69 assert response.json() == {"detail": "Not authenticated"} 1QRSTUV

70 assert response.headers["WWW-Authenticate"] == "Bearer" 1QRSTUV

71 

72 

73def test_token(mod: ModuleType): 1abcdef

74 client = TestClient(mod.app) 1hlptxB

75 access_token = get_access_token(scope="me", client=client) 1hlptxB

76 response = client.get( 1hlptxB

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

78 ) 

79 assert response.status_code == 200, response.text 1hlptxB

80 assert response.json() == { 1hlptxB

81 "username": "johndoe", 

82 "full_name": "John Doe", 

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

84 "disabled": False, 

85 } 

86 

87 

88def test_incorrect_token(mod: ModuleType): 1abcdef

89 client = TestClient(mod.app) 1WXYZ01

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

91 assert response.status_code == 401, response.text 1WXYZ01

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

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

94 

95 

96def test_incorrect_token_type(mod: ModuleType): 1abcdef

97 client = TestClient(mod.app) 1234567

98 response = client.get( 1234567

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

100 ) 

101 assert response.status_code == 401, response.text 1234567

102 assert response.json() == {"detail": "Not authenticated"} 1234567

103 assert response.headers["WWW-Authenticate"] == "Bearer" 1234567

104 

105 

106def test_verify_password(mod: ModuleType): 1abcdef

107 assert mod.verify_password( 2tbubvbwbxbyb

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

109 ) 

110 

111 

112def test_get_password_hash(mod: ModuleType): 1abcdef

113 assert mod.get_password_hash("secretalice") 2zbAbBbCbDbEb

114 

115 

116def test_create_access_token(mod: ModuleType): 1abcdef

117 access_token = mod.create_access_token(data={"data": "foo"}) 2nbobpbqbrbsb

118 assert access_token 2nbobpbqbrbsb

119 

120 

121def test_token_no_sub(mod: ModuleType): 1abcdef

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

123 

124 response = client.get( 189!#$%

125 "/users/me", 

126 headers={ 

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

128 }, 

129 ) 

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

131 assert response.json() == {"detail": "Could not validate credentials"} 189!#$%

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

133 

134 

135def test_token_no_username(mod: ModuleType): 1abcdef

136 client = TestClient(mod.app) 1'()*+,

137 

138 response = client.get( 1'()*+,

139 "/users/me", 

140 headers={ 

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

142 }, 

143 ) 

144 assert response.status_code == 401, response.text 1'()*+,

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

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

147 

148 

149def test_token_no_scope(mod: ModuleType): 1abcdef

150 client = TestClient(mod.app) 1jnrvzD

151 

152 access_token = get_access_token(client=client) 1jnrvzD

153 response = client.get( 1jnrvzD

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

155 ) 

156 assert response.status_code == 401, response.text 1jnrvzD

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

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

159 

160 

161def test_token_nonexistent_user(mod: ModuleType): 1abcdef

162 client = TestClient(mod.app) 1-./:;=

163 

164 response = client.get( 1-./:;=

165 "/users/me", 

166 headers={ 

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

168 }, 

169 ) 

170 assert response.status_code == 401, response.text 1-./:;=

171 assert response.json() == {"detail": "Could not validate credentials"} 1-./:;=

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

173 

174 

175def test_token_inactive_user(mod: ModuleType): 1abcdef

176 client = TestClient(mod.app) 1imquyC

177 

178 access_token = get_access_token( 1imquyC

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

180 ) 

181 response = client.get( 1imquyC

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

183 ) 

184 assert response.status_code == 400, response.text 1imquyC

185 assert response.json() == {"detail": "Inactive user"} 1imquyC

186 

187 

188def test_read_items(mod: ModuleType): 1abcdef

189 client = TestClient(mod.app) 1gkoswA

190 access_token = get_access_token(scope="me items", client=client) 1gkoswA

191 response = client.get( 1gkoswA

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

193 ) 

194 assert response.status_code == 200, response.text 1gkoswA

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

196 

197 

198def test_read_system_status(mod: ModuleType): 1abcdef

199 client = TestClient(mod.app) 1EFGHIJ

200 access_token = get_access_token(client=client) 1EFGHIJ

201 response = client.get( 1EFGHIJ

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

203 ) 

204 assert response.status_code == 200, response.text 1EFGHIJ

205 assert response.json() == {"status": "ok"} 1EFGHIJ

206 

207 

208def test_read_system_status_no_token(mod: ModuleType): 1abcdef

209 client = TestClient(mod.app) 1?@[]^_

210 response = client.get("/status/") 1?@[]^_

211 assert response.status_code == 401, response.text 1?@[]^_

212 assert response.json() == {"detail": "Not authenticated"} 1?@[]^_

213 assert response.headers["WWW-Authenticate"] == "Bearer" 1?@[]^_

214 

215 

216def test_openapi_schema(mod: ModuleType): 1abcdef

217 client = TestClient(mod.app) 2hbibjbkblbmb

218 response = client.get("/openapi.json") 2hbibjbkblbmb

219 assert response.status_code == 200, response.text 2hbibjbkblbmb

220 assert response.json() == { 2hbibjbkblbmb

221 "openapi": "3.1.0", 

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

223 "paths": { 

224 "/token": { 

225 "post": { 

226 "responses": { 

227 "200": { 

228 "description": "Successful Response", 

229 "content": { 

230 "application/json": { 

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

232 } 

233 }, 

234 }, 

235 "422": { 

236 "description": "Validation Error", 

237 "content": { 

238 "application/json": { 

239 "schema": { 

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

241 } 

242 } 

243 }, 

244 }, 

245 }, 

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

247 "operationId": "login_for_access_token_token_post", 

248 "requestBody": { 

249 "content": { 

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

251 "schema": { 

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

253 } 

254 } 

255 }, 

256 "required": True, 

257 }, 

258 } 

259 }, 

260 "/users/me/": { 

261 "get": { 

262 "responses": { 

263 "200": { 

264 "description": "Successful Response", 

265 "content": { 

266 "application/json": { 

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

268 } 

269 }, 

270 } 

271 }, 

272 "summary": "Read Users Me", 

273 "operationId": "read_users_me_users_me__get", 

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

275 } 

276 }, 

277 "/users/me/items/": { 

278 "get": { 

279 "responses": { 

280 "200": { 

281 "description": "Successful Response", 

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

283 } 

284 }, 

285 "summary": "Read Own Items", 

286 "operationId": "read_own_items_users_me_items__get", 

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

288 } 

289 }, 

290 "/status/": { 

291 "get": { 

292 "responses": { 

293 "200": { 

294 "description": "Successful Response", 

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

296 } 

297 }, 

298 "summary": "Read System Status", 

299 "operationId": "read_system_status_status__get", 

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

301 } 

302 }, 

303 }, 

304 "components": { 

305 "schemas": { 

306 "User": { 

307 "title": "User", 

308 "required": IsOneOf( 

309 ["username", "email", "full_name", "disabled"], 

310 # TODO: remove when deprecating Pydantic v1 

311 ["username"], 

312 ), 

313 "type": "object", 

314 "properties": { 

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

316 "email": IsDict( 

317 { 

318 "title": "Email", 

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

320 } 

321 ) 

322 | IsDict( 

323 # TODO: remove when deprecating Pydantic v1 

324 {"title": "Email", "type": "string"} 

325 ), 

326 "full_name": IsDict( 

327 { 

328 "title": "Full Name", 

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

330 } 

331 ) 

332 | IsDict( 

333 # TODO: remove when deprecating Pydantic v1 

334 {"title": "Full Name", "type": "string"} 

335 ), 

336 "disabled": IsDict( 

337 { 

338 "title": "Disabled", 

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

340 } 

341 ) 

342 | IsDict( 

343 # TODO: remove when deprecating Pydantic v1 

344 {"title": "Disabled", "type": "boolean"} 

345 ), 

346 }, 

347 }, 

348 "Token": { 

349 "title": "Token", 

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

351 "type": "object", 

352 "properties": { 

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

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

355 }, 

356 }, 

357 "Body_login_for_access_token_token_post": { 

358 "title": "Body_login_for_access_token_token_post", 

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

360 "type": "object", 

361 "properties": { 

362 "grant_type": IsDict( 

363 { 

364 "title": "Grant Type", 

365 "anyOf": [ 

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

367 {"type": "null"}, 

368 ], 

369 } 

370 ) 

371 | IsDict( 

372 # TODO: remove when deprecating Pydantic v1 

373 { 

374 "title": "Grant Type", 

375 "pattern": "^password$", 

376 "type": "string", 

377 } 

378 ), 

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

380 "password": {"title": "Password", "type": "string"}, 

381 "scope": {"title": "Scope", "type": "string", "default": ""}, 

382 "client_id": IsDict( 

383 { 

384 "title": "Client Id", 

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

386 } 

387 ) 

388 | IsDict( 

389 # TODO: remove when deprecating Pydantic v1 

390 {"title": "Client Id", "type": "string"} 

391 ), 

392 "client_secret": IsDict( 

393 { 

394 "title": "Client Secret", 

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

396 } 

397 ) 

398 | IsDict( 

399 # TODO: remove when deprecating Pydantic v1 

400 {"title": "Client Secret", "type": "string"} 

401 ), 

402 }, 

403 }, 

404 "ValidationError": { 

405 "title": "ValidationError", 

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

407 "type": "object", 

408 "properties": { 

409 "loc": { 

410 "title": "Location", 

411 "type": "array", 

412 "items": { 

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

414 }, 

415 }, 

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

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

418 }, 

419 }, 

420 "HTTPValidationError": { 

421 "title": "HTTPValidationError", 

422 "type": "object", 

423 "properties": { 

424 "detail": { 

425 "title": "Detail", 

426 "type": "array", 

427 "items": {"$ref": "#/components/schemas/ValidationError"}, 

428 } 

429 }, 

430 }, 

431 }, 

432 "securitySchemes": { 

433 "OAuth2PasswordBearer": { 

434 "type": "oauth2", 

435 "flows": { 

436 "password": { 

437 "scopes": { 

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

439 "items": "Read items.", 

440 }, 

441 "tokenUrl": "token", 

442 } 

443 }, 

444 } 

445 }, 

446 }, 

447 }