Skip to content

Commit 2f3123c

Browse files
authored
Merge pull request #148 from YousefAldabbas/#147-enhance-test-setup
enhance test setup
2 parents 3c646e7 + a0ed558 commit 2f3123c

File tree

9 files changed

+138
-78
lines changed

9 files changed

+138
-78
lines changed

README.md

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -267,16 +267,6 @@ DEFAULT_RATE_LIMIT_LIMIT=10 # default=10
267267
DEFAULT_RATE_LIMIT_PERIOD=3600 # default=3600
268268
```
269269

270-
For tests (optional to run):
271-
272-
```
273-
# ------------- test -------------
274-
TEST_NAME="Tester User"
275-
TEST_EMAIL="[email protected]"
276-
TEST_USERNAME="testeruser"
277-
TEST_PASSWORD="Str1ng$t"
278-
```
279-
280270
And Finally the environment:
281271

282272
```
@@ -553,9 +543,11 @@ First, you may want to take a look at the project structure and understand what
553543
├── LICENSE.md # License file for the project.
554544
555545
├── tests # Unit and integration tests for the application.
546+
│ ├──helpers # Helper functions for tests.
547+
│ │ ├── generators.py # Helper functions for generating test data.
548+
│ │ └── mocks.py # Mock function for testing.
556549
│ ├── __init__.py
557550
│ ├── conftest.py # Configuration and fixtures for pytest.
558-
│ ├── helper.py # Helper functions for tests.
559551
│ └── test_user.py # Test cases for user-related functionality.
560552
561553
└── src # Source code directory.
@@ -1842,16 +1834,6 @@ And finally, on your browser: `http://localhost/docs`.
18421834

18431835
## 7. Testing
18441836

1845-
For tests, ensure you have in `.env`:
1846-
1847-
```
1848-
# ------------- test -------------
1849-
TEST_NAME="Tester User"
1850-
TEST_EMAIL="[email protected]"
1851-
TEST_USERNAME="testeruser"
1852-
TEST_PASSWORD="Str1ng$t"
1853-
```
1854-
18551837
While in the tests folder, create your test file with the name "test\_{entity}.py", replacing entity with what you're testing
18561838

18571839
```sh
@@ -1876,7 +1858,6 @@ First you need to uncomment the following part in the `docker-compose.yml` file:
18761858
# - ./src/.env
18771859
# depends_on:
18781860
# - db
1879-
# - create_superuser
18801861
# - redis
18811862
# command: python -m pytest ./tests
18821863
# volumes:
@@ -1895,7 +1876,6 @@ You'll get:
18951876
- ./src/.env
18961877
depends_on:
18971878
- db
1898-
- create_superuser
18991879
- redis
19001880
command: python -m pytest ./tests
19011881
volumes:

docker-compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ services:
102102
# - ./src/.env
103103
# depends_on:
104104
# - db
105-
# - create_superuser
106105
# - redis
107106
# command: python -m pytest ./tests
108107
# volumes:

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ arq = "^0.25.0"
3131
gunicorn = "^22.0.0"
3232
bcrypt = "^4.1.1"
3333
fastcrud = "^0.12.0"
34+
faker = "^26.0.0"
35+
psycopg2-binary = "^2.9.9"
36+
pytest-mock = "^3.14.0"
3437

3538

3639
[build-system]

src/app/core/config.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,7 @@ class FirstUserSettings(BaseSettings):
6767

6868

6969
class TestSettings(BaseSettings):
70-
TEST_NAME: str = config("TEST_NAME", default="Tester User")
71-
TEST_EMAIL: str = config("TEST_EMAIL", default="[email protected]")
72-
TEST_USERNAME: str = config("TEST_USERNAME", default="testeruser")
73-
TEST_PASSWORD: str = config("TEST_PASSWORD", default="Str1ng$t")
70+
...
7471

7572

7673
class RedisCacheSettings(BaseSettings):

tests/conftest.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,39 @@
1+
from typing import Any, Callable, Generator
2+
13
import pytest
4+
from faker import Faker
25
from fastapi.testclient import TestClient
6+
from sqlalchemy import create_engine
7+
from sqlalchemy.orm import sessionmaker
8+
from sqlalchemy.orm.session import Session
39

10+
from src.app.core.config import settings
411
from src.app.main import app
512

13+
DATABASE_URI = settings.POSTGRES_URI
14+
DATABASE_PREFIX = settings.POSTGRES_SYNC_PREFIX
15+
16+
sync_engine = create_engine(DATABASE_PREFIX + DATABASE_URI)
17+
local_session = sessionmaker(autocommit=False, autoflush=False, bind=sync_engine)
18+
19+
20+
fake = Faker()
21+
622

723
@pytest.fixture(scope="session")
8-
def client():
24+
def client() -> Generator[TestClient, Any, None]:
925
with TestClient(app) as _client:
1026
yield _client
27+
app.dependency_overrides = {}
28+
sync_engine.dispose()
29+
30+
31+
@pytest.fixture
32+
def db() -> Generator[Session, Any, None]:
33+
session = local_session()
34+
yield session
35+
session.close()
36+
37+
38+
def override_dependency(dependency: Callable[..., Any], mocked_response: Any) -> None:
39+
app.dependency_overrides[dependency] = lambda: mocked_response

tests/helper.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/helpers/generators.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import uuid as uuid_pkg
2+
3+
from sqlalchemy.orm import Session
4+
5+
from src.app import models
6+
from src.app.core.security import get_password_hash
7+
from tests.conftest import fake
8+
9+
10+
def create_user(db: Session, is_super_user: bool = False) -> models.User:
11+
_user = models.User(
12+
name=fake.name(),
13+
username=fake.user_name(),
14+
email=fake.email(),
15+
hashed_password=get_password_hash(fake.password()),
16+
profile_image_url=fake.image_url(),
17+
uuid=uuid_pkg.uuid4(),
18+
is_superuser=is_super_user,
19+
)
20+
21+
db.add(_user)
22+
db.commit()
23+
db.refresh(_user)
24+
25+
return _user
26+

tests/helpers/mocks.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from typing import Any
2+
3+
from fastapi.encoders import jsonable_encoder
4+
5+
from src.app import models
6+
from tests.conftest import fake
7+
8+
9+
def get_current_user(user: models.User) -> dict[str, Any]:
10+
return jsonable_encoder(user)
11+
12+
13+
def oauth2_scheme() -> str:
14+
token = fake.sha256()
15+
if isinstance(token, bytes):
16+
token = token.decode("utf-8")
17+
return token # type: ignore

tests/test_user.py

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,81 @@
1+
from fastapi import status
12
from fastapi.testclient import TestClient
3+
from pytest_mock import MockerFixture
4+
from sqlalchemy.orm import Session
25

3-
from src.app.core.config import settings
4-
from src.app.main import app
6+
from src.app.api.dependencies import get_current_user
7+
from src.app.api.v1.users import oauth2_scheme
8+
from tests.conftest import fake, override_dependency
59

6-
from .helper import _get_token
7-
8-
test_name = settings.TEST_NAME
9-
test_username = settings.TEST_USERNAME
10-
test_email = settings.TEST_EMAIL
11-
test_password = settings.TEST_PASSWORD
12-
13-
admin_username = settings.ADMIN_USERNAME
14-
admin_password = settings.ADMIN_PASSWORD
15-
16-
client = TestClient(app)
10+
from .helpers import generators, mocks
1711

1812

1913
def test_post_user(client: TestClient) -> None:
2014
response = client.post(
2115
"/api/v1/user",
22-
json={"name": test_name, "username": test_username, "email": test_email, "password": test_password},
16+
json={
17+
"name": fake.name(),
18+
"username": fake.user_name(),
19+
"email": fake.email(),
20+
"password": fake.password(),
21+
},
2322
)
24-
assert response.status_code == 201
23+
assert response.status_code == status.HTTP_201_CREATED
24+
2525

26+
def test_get_user(db: Session, client: TestClient) -> None:
27+
user = generators.create_user(db)
2628

27-
def test_get_user(client: TestClient) -> None:
28-
response = client.get(f"/api/v1/user/{test_username}")
29-
assert response.status_code == 200
29+
response = client.get(f"/api/v1/user/{user.username}")
30+
assert response.status_code == status.HTTP_200_OK
3031

32+
response_data = response.json()
33+
34+
assert response_data["id"] == user.id
35+
assert response_data["username"] == user.username
36+
37+
38+
def test_get_multiple_users(db: Session, client: TestClient) -> None:
39+
for _ in range(5):
40+
generators.create_user(db)
3141

32-
def test_get_multiple_users(client: TestClient) -> None:
3342
response = client.get("/api/v1/users")
34-
assert response.status_code == 200
43+
assert response.status_code == status.HTTP_200_OK
3544

45+
response_data = response.json()["data"]
46+
assert len(response_data) >= 5
3647

37-
def test_update_user(client: TestClient) -> None:
38-
token = _get_token(username=test_username, password=test_password, client=client)
3948

40-
response = client.patch(
41-
f"/api/v1/user/{test_username}",
42-
json={"name": f"Updated {test_name}"},
43-
headers={"Authorization": f'Bearer {token.json()["access_token"]}'},
44-
)
45-
assert response.status_code == 200
49+
def test_update_user(db: Session, client: TestClient) -> None:
50+
user = generators.create_user(db)
51+
new_name = fake.name()
4652

53+
override_dependency(get_current_user, mocks.get_current_user(user))
4754

48-
def test_delete_user(client: TestClient) -> None:
49-
token = _get_token(username=test_username, password=test_password, client=client)
55+
response = client.patch(f"/api/v1/user/{user.username}", json={"name": new_name})
56+
assert response.status_code == status.HTTP_200_OK
5057

51-
response = client.delete(
52-
f"/api/v1/user/{test_username}", headers={"Authorization": f'Bearer {token.json()["access_token"]}'}
53-
)
54-
assert response.status_code == 200
5558

59+
def test_delete_user(db: Session, client: TestClient, mocker: MockerFixture) -> None:
60+
user = generators.create_user(db)
5661

57-
def test_delete_db_user(client: TestClient) -> None:
58-
token = _get_token(username=admin_username, password=admin_password, client=client)
62+
override_dependency(get_current_user, mocks.get_current_user(user))
63+
override_dependency(oauth2_scheme, mocks.oauth2_scheme())
5964

60-
response = client.delete(
61-
f"/api/v1/db_user/{test_username}", headers={"Authorization": f'Bearer {token.json()["access_token"]}'}
62-
)
63-
assert response.status_code == 200
65+
mocker.patch("src.app.core.security.jwt.decode", return_value={"sub": user.username, "exp": 9999999999})
66+
67+
response = client.delete(f"/api/v1/user/{user.username}")
68+
assert response.status_code == status.HTTP_200_OK
69+
70+
71+
def test_delete_db_user(db: Session, mocker: MockerFixture, client: TestClient) -> None:
72+
user = generators.create_user(db)
73+
super_user = generators.create_user(db, is_super_user=True)
74+
75+
override_dependency(get_current_user, mocks.get_current_user(super_user))
76+
override_dependency(oauth2_scheme, mocks.oauth2_scheme())
77+
78+
mocker.patch("src.app.core.security.jwt.decode", return_value={"sub": user.username, "exp": 9999999999})
79+
80+
response = client.delete(f"/api/v1/db_user/{user.username}")
81+
assert response.status_code == status.HTTP_200_OK

0 commit comments

Comments
 (0)