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

125 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-08-08 03:53 +0000

1import pytest 1eabcd

2from dirty_equals import IsDict, IsOneOf 1eabcd

3from fastapi.testclient import TestClient 1eabcd

4 

5from ...utils import needs_py39 1eabcd

6 

7 

8@pytest.fixture(name="client") 1eabcd

9def get_client(): 1eabcd

10 from docs_src.security.tutorial005_py39 import app 1abcd

11 

12 client = TestClient(app) 1abcd

13 return client 1abcd

14 

15 

16def get_access_token( 1eabcd

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

18): 

19 data = {"username": username, "password": password} 1abcd

20 if scope: 1abcd

21 data["scope"] = scope 1abcd

22 response = client.post("/token", data=data) 1abcd

23 content = response.json() 1abcd

24 access_token = content.get("access_token") 1abcd

25 return access_token 1abcd

26 

27 

28@needs_py39 1eabcd

29def test_login(client: TestClient): 1eabcd

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

31 assert response.status_code == 200, response.text 1abcd

32 content = response.json() 1abcd

33 assert "access_token" in content 1abcd

34 assert content["token_type"] == "bearer" 1abcd

35 

36 

37@needs_py39 1eabcd

38def test_login_incorrect_password(client: TestClient): 1eabcd

39 response = client.post( 1abcd

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

41 ) 

42 assert response.status_code == 400, response.text 1abcd

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

44 

45 

46@needs_py39 1eabcd

47def test_login_incorrect_username(client: TestClient): 1eabcd

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

49 assert response.status_code == 400, response.text 1abcd

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

51 

52 

53@needs_py39 1eabcd

54def test_no_token(client: TestClient): 1eabcd

55 response = client.get("/users/me") 1abcd

56 assert response.status_code == 401, response.text 1abcd

57 assert response.json() == {"detail": "Not authenticated"} 1abcd

58 assert response.headers["WWW-Authenticate"] == "Bearer" 1abcd

59 

60 

61@needs_py39 1eabcd

62def test_token(client: TestClient): 1eabcd

63 access_token = get_access_token(scope="me", client=client) 1abcd

64 response = client.get( 1abcd

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

66 ) 

67 assert response.status_code == 200, response.text 1abcd

68 assert response.json() == { 1abcd

69 "username": "johndoe", 

70 "full_name": "John Doe", 

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

72 "disabled": False, 

73 } 

74 

75 

76@needs_py39 1eabcd

77def test_incorrect_token(client: TestClient): 1eabcd

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

79 assert response.status_code == 401, response.text 1abcd

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

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

82 

83 

84@needs_py39 1eabcd

85def test_incorrect_token_type(client: TestClient): 1eabcd

86 response = client.get( 1abcd

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

88 ) 

89 assert response.status_code == 401, response.text 1abcd

90 assert response.json() == {"detail": "Not authenticated"} 1abcd

91 assert response.headers["WWW-Authenticate"] == "Bearer" 1abcd

92 

93 

94@needs_py39 1eabcd

95def test_verify_password(): 1eabcd

96 from docs_src.security.tutorial005_py39 import fake_users_db, verify_password 1abcd

97 

98 assert verify_password("secret", fake_users_db["johndoe"]["hashed_password"]) 1abcd

99 

100 

101@needs_py39 1eabcd

102def test_get_password_hash(): 1eabcd

103 from docs_src.security.tutorial005_py39 import get_password_hash 1abcd

104 

105 assert get_password_hash("secretalice") 1abcd

106 

107 

108@needs_py39 1eabcd

109def test_create_access_token(): 1eabcd

110 from docs_src.security.tutorial005_py39 import create_access_token 1abcd

111 

112 access_token = create_access_token(data={"data": "foo"}) 1abcd

113 assert access_token 1abcd

114 

115 

116@needs_py39 1eabcd

117def test_token_no_sub(client: TestClient): 1eabcd

118 response = client.get( 1abcd

119 "/users/me", 

120 headers={ 

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

122 }, 

123 ) 

124 assert response.status_code == 401, response.text 1abcd

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

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

127 

128 

129@needs_py39 1eabcd

130def test_token_no_username(client: TestClient): 1eabcd

131 response = client.get( 1abcd

132 "/users/me", 

133 headers={ 

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

135 }, 

136 ) 

137 assert response.status_code == 401, response.text 1abcd

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

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

140 

141 

142@needs_py39 1eabcd

143def test_token_no_scope(client: TestClient): 1eabcd

144 access_token = get_access_token(client=client) 1abcd

145 response = client.get( 1abcd

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

147 ) 

148 assert response.status_code == 401, response.text 1abcd

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

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

151 

152 

153@needs_py39 1eabcd

154def test_token_nonexistent_user(client: TestClient): 1eabcd

155 response = client.get( 1abcd

156 "/users/me", 

157 headers={ 

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

159 }, 

160 ) 

161 assert response.status_code == 401, response.text 1abcd

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

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

164 

165 

166@needs_py39 1eabcd

167def test_token_inactive_user(client: TestClient): 1eabcd

168 access_token = get_access_token( 1abcd

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

170 ) 

171 response = client.get( 1abcd

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

173 ) 

174 assert response.status_code == 400, response.text 1abcd

175 assert response.json() == {"detail": "Inactive user"} 1abcd

176 

177 

178@needs_py39 1eabcd

179def test_read_items(client: TestClient): 1eabcd

180 access_token = get_access_token(scope="me items", client=client) 1abcd

181 response = client.get( 1abcd

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

183 ) 

184 assert response.status_code == 200, response.text 1abcd

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

186 

187 

188@needs_py39 1eabcd

189def test_read_system_status(client: TestClient): 1eabcd

190 access_token = get_access_token(client=client) 1abcd

191 response = client.get( 1abcd

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

193 ) 

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

195 assert response.json() == {"status": "ok"} 1abcd

196 

197 

198@needs_py39 1eabcd

199def test_read_system_status_no_token(client: TestClient): 1eabcd

200 response = client.get("/status/") 1abcd

201 assert response.status_code == 401, response.text 1abcd

202 assert response.json() == {"detail": "Not authenticated"} 1abcd

203 assert response.headers["WWW-Authenticate"] == "Bearer" 1abcd

204 

205 

206@needs_py39 1eabcd

207def test_openapi_schema(client: TestClient): 1eabcd

208 response = client.get("/openapi.json") 1abcd

209 assert response.status_code == 200, response.text 1abcd

210 assert response.json() == { 1abcd

211 "openapi": "3.1.0", 

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

213 "paths": { 

214 "/token": { 

215 "post": { 

216 "responses": { 

217 "200": { 

218 "description": "Successful Response", 

219 "content": { 

220 "application/json": { 

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

222 } 

223 }, 

224 }, 

225 "422": { 

226 "description": "Validation Error", 

227 "content": { 

228 "application/json": { 

229 "schema": { 

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

231 } 

232 } 

233 }, 

234 }, 

235 }, 

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

237 "operationId": "login_for_access_token_token_post", 

238 "requestBody": { 

239 "content": { 

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

241 "schema": { 

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

243 } 

244 } 

245 }, 

246 "required": True, 

247 }, 

248 } 

249 }, 

250 "/users/me/": { 

251 "get": { 

252 "responses": { 

253 "200": { 

254 "description": "Successful Response", 

255 "content": { 

256 "application/json": { 

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

258 } 

259 }, 

260 } 

261 }, 

262 "summary": "Read Users Me", 

263 "operationId": "read_users_me_users_me__get", 

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

265 } 

266 }, 

267 "/users/me/items/": { 

268 "get": { 

269 "responses": { 

270 "200": { 

271 "description": "Successful Response", 

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

273 } 

274 }, 

275 "summary": "Read Own Items", 

276 "operationId": "read_own_items_users_me_items__get", 

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

278 } 

279 }, 

280 "/status/": { 

281 "get": { 

282 "responses": { 

283 "200": { 

284 "description": "Successful Response", 

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

286 } 

287 }, 

288 "summary": "Read System Status", 

289 "operationId": "read_system_status_status__get", 

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

291 } 

292 }, 

293 }, 

294 "components": { 

295 "schemas": { 

296 "User": { 

297 "title": "User", 

298 "required": IsOneOf( 

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

300 # TODO: remove when deprecating Pydantic v1 

301 ["username"], 

302 ), 

303 "type": "object", 

304 "properties": { 

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

306 "email": IsDict( 

307 { 

308 "title": "Email", 

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

310 } 

311 ) 

312 | IsDict( 

313 # TODO: remove when deprecating Pydantic v1 

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

315 ), 

316 "full_name": IsDict( 

317 { 

318 "title": "Full Name", 

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

320 } 

321 ) 

322 | IsDict( 

323 # TODO: remove when deprecating Pydantic v1 

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

325 ), 

326 "disabled": IsDict( 

327 { 

328 "title": "Disabled", 

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

330 } 

331 ) 

332 | IsDict( 

333 # TODO: remove when deprecating Pydantic v1 

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

335 ), 

336 }, 

337 }, 

338 "Token": { 

339 "title": "Token", 

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

341 "type": "object", 

342 "properties": { 

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

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

345 }, 

346 }, 

347 "Body_login_for_access_token_token_post": { 

348 "title": "Body_login_for_access_token_token_post", 

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

350 "type": "object", 

351 "properties": { 

352 "grant_type": IsDict( 

353 { 

354 "title": "Grant Type", 

355 "anyOf": [ 

356 {"pattern": "password", "type": "string"}, 

357 {"type": "null"}, 

358 ], 

359 } 

360 ) 

361 | IsDict( 

362 # TODO: remove when deprecating Pydantic v1 

363 { 

364 "title": "Grant Type", 

365 "pattern": "password", 

366 "type": "string", 

367 } 

368 ), 

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

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

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

372 "client_id": IsDict( 

373 { 

374 "title": "Client Id", 

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

376 } 

377 ) 

378 | IsDict( 

379 # TODO: remove when deprecating Pydantic v1 

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

381 ), 

382 "client_secret": IsDict( 

383 { 

384 "title": "Client Secret", 

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

386 } 

387 ) 

388 | IsDict( 

389 # TODO: remove when deprecating Pydantic v1 

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

391 ), 

392 }, 

393 }, 

394 "ValidationError": { 

395 "title": "ValidationError", 

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

397 "type": "object", 

398 "properties": { 

399 "loc": { 

400 "title": "Location", 

401 "type": "array", 

402 "items": { 

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

404 }, 

405 }, 

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

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

408 }, 

409 }, 

410 "HTTPValidationError": { 

411 "title": "HTTPValidationError", 

412 "type": "object", 

413 "properties": { 

414 "detail": { 

415 "title": "Detail", 

416 "type": "array", 

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

418 } 

419 }, 

420 }, 

421 }, 

422 "securitySchemes": { 

423 "OAuth2PasswordBearer": { 

424 "type": "oauth2", 

425 "flows": { 

426 "password": { 

427 "scopes": { 

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

429 "items": "Read items.", 

430 }, 

431 "tokenUrl": "token", 

432 } 

433 }, 

434 } 

435 }, 

436 }, 

437 }