Skip to content

Commit 703e014

Browse files
committed
- dict() -> model_dump() (Pydantic 2 stuff),
- lang/lang_version in saved script schemas - lang/lang_version corresponding update in Editor component - more unit tests (overall coverage ~95%) - extra badge in readme
1 parent b25e5c3 commit 703e014

32 files changed

+5020
-19
lines changed

.github/workflows/tests.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ jobs:
115115
cd backend
116116
python -m pytest tests/integration -v --cov=app --cov-report=xml --cov-report=term
117117
118+
- name: Upload coverage to Codecov
119+
uses: codecov/codecov-action@v3
120+
with:
121+
file: backend/coverage.xml
122+
flags: backend
123+
name: backend-coverage
124+
fail_ci_if_error: false
125+
118126
- name: Collect logs
119127
if: always()
120128
run: |

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
<a href="https://github.com/HardMax71/Integr8sCode/actions/workflows/tests.yml">
1919
<img src="https://img.shields.io/github/actions/workflow/status/HardMax71/Integr8sCode/tests.yml?branch=main&label=tests&logo=pytest" alt="Tests Status" />
2020
</a>
21+
<a href="https://codecov.io/gh/HardMax71/Integr8sCode">
22+
<img src="https://img.shields.io/codecov/c/github/HardMax71/Integr8sCode?flag=backend&label=backend%20coverage&logo=codecov" alt="Backend Coverage" />
23+
</a>
2124
</p>
2225
<p align="center">
2326
<a href="https://sonarcloud.io/dashboard?id=HardMax71_Integr8sCode">

backend/app/db/repositories/execution_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def __init__(self, db: AsyncIOMotorDatabase):
1414
self.collection: AsyncIOMotorCollection = self.db.get_collection("executions")
1515

1616
async def create_execution(self, execution: ExecutionInDB) -> str:
17-
execution_dict = execution.dict(by_alias=True)
17+
execution_dict = execution.model_dump(by_alias=True)
1818
await self.collection.insert_one(execution_dict)
1919
return str(execution.id)
2020

backend/app/db/repositories/saved_script_repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def __init__(self, db: AsyncIOMotorDatabase):
1111
self.db = db
1212

1313
async def create_saved_script(self, saved_script: SavedScriptInDB) -> str:
14-
saved_script_dict = saved_script.dict(by_alias=True)
14+
saved_script_dict = saved_script.model_dump(by_alias=True)
1515
await self.db.saved_scripts.insert_one(saved_script_dict)
1616
return str(saved_script.id)
1717

backend/app/schemas/saved_script.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
class SavedScriptBase(BaseModel):
99
name: str
1010
script: str
11+
lang: str = "python"
12+
lang_version: str = "3.11"
1113
description: Optional[str] = None
1214

1315

@@ -29,6 +31,8 @@ class Config:
2931
class SavedScriptUpdate(BaseModel):
3032
name: Optional[str] = None
3133
script: Optional[str] = None
34+
lang: Optional[str] = None
35+
lang_version: Optional[str] = None
3236
description: Optional[str] = None
3337
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
3438

@@ -41,6 +45,8 @@ class SavedScriptResponse(BaseModel):
4145
id: str
4246
name: str
4347
script: str
48+
lang: str
49+
lang_version: str
4450
description: Optional[str] = None
4551
created_at: datetime
4652
updated_at: datetime

backend/app/services/saved_script_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ async def create_saved_script(
2121
self, saved_script_create: SavedScriptCreate, user_id: str
2222
) -> SavedScriptInDB:
2323
saved_script_in_db = SavedScriptInDB(
24-
**saved_script_create.dict(), user_id=user_id
24+
**saved_script_create.model_dump(), user_id=user_id
2525
)
2626
await self.saved_script_repo.create_saved_script(saved_script_in_db)
2727
return saved_script_in_db
@@ -32,7 +32,7 @@ async def get_saved_script(self, script_id: str, user_id: str) -> Optional[Saved
3232
async def update_saved_script(
3333
self, script_id: str, user_id: str, update_data: SavedScriptUpdate
3434
) -> None:
35-
update_dict = update_data.dict(exclude_unset=True)
35+
update_dict = update_data.model_dump(exclude_unset=True)
3636
update_dict["updated_at"] = datetime.now(timezone.utc)
3737
await self.saved_script_repo.update_saved_script(
3838
script_id, user_id, update_dict

backend/tests/unit/__init__.py

Whitespace-only changes.

backend/tests/unit/api/__init__.py

Whitespace-only changes.

backend/tests/unit/api/routes/__init__.py

Whitespace-only changes.
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
from unittest.mock import AsyncMock, Mock, patch
2+
3+
import pytest
4+
from app.api.routes.auth import login, register, verify_token, logout
5+
from app.schemas.user import UserCreate
6+
from fastapi import HTTPException, Request, Response
7+
from fastapi.security import OAuth2PasswordRequestForm
8+
9+
10+
class TestAuthRoutesCoverage:
11+
12+
@pytest.fixture(autouse=True)
13+
def setup(self) -> None:
14+
self.mock_request = Mock(spec=Request)
15+
self.mock_response = Mock(spec=Response)
16+
self.mock_response.set_cookie = Mock()
17+
self.mock_response.delete_cookie = Mock()
18+
self.mock_user_repo = AsyncMock()
19+
20+
def _setup_request(self, has_user_agent: bool = True) -> None:
21+
if has_user_agent:
22+
self.mock_request.headers = {"user-agent": "test-agent"}
23+
else:
24+
self.mock_request.headers = {}
25+
26+
@pytest.mark.asyncio
27+
async def test_login_scenarios(self) -> None:
28+
login_cases = [
29+
("nonexistent", "password", None, False, 401, "Invalid credentials"),
30+
("testuser", "wrongpassword", Mock(username="testuser"), False, 401, "Invalid credentials"),
31+
("testuser", "correctpassword", Mock(username="testuser"), True, None, None)
32+
]
33+
34+
for username, password, user_return, verify_password_result, expected_status, expected_detail in login_cases:
35+
self._setup_request()
36+
37+
mock_form_data = Mock(spec=OAuth2PasswordRequestForm)
38+
mock_form_data.username = username
39+
mock_form_data.password = password
40+
41+
self.mock_user_repo.get_user.return_value = user_return
42+
43+
with patch('app.api.routes.auth.get_remote_address', return_value="127.0.0.1"):
44+
if expected_status:
45+
with patch('app.api.routes.auth.security_service.verify_password',
46+
return_value=verify_password_result):
47+
with pytest.raises(HTTPException) as exc_info:
48+
await login(self.mock_request, self.mock_response, mock_form_data, self.mock_user_repo)
49+
50+
assert exc_info.value.status_code == expected_status
51+
assert exc_info.value.detail == expected_detail
52+
else:
53+
mock_settings = Mock(ACCESS_TOKEN_EXPIRE_MINUTES=30)
54+
with patch('app.api.routes.auth.security_service.verify_password', return_value=True):
55+
with patch('app.api.routes.auth.get_settings', return_value=mock_settings):
56+
with patch('app.api.routes.auth.security_service.create_access_token',
57+
return_value="token"):
58+
with patch('app.api.routes.auth.security_service.generate_csrf_token',
59+
return_value="csrf"):
60+
result = await login(self.mock_request, self.mock_response, mock_form_data,
61+
self.mock_user_repo)
62+
63+
assert result["message"] == "Login successful"
64+
assert result["username"] == "testuser"
65+
assert result["csrf_token"] == "csrf"
66+
assert self.mock_response.set_cookie.call_count == 2
67+
68+
self.mock_user_repo.reset_mock()
69+
self.mock_response.reset_mock()
70+
71+
@pytest.mark.asyncio
72+
async def test_register_scenarios(self) -> None:
73+
register_cases = [
74+
(Mock(), 400, "Username already registered", None),
75+
(None, 500, "Error creating user", Exception("Database error")),
76+
(None, None, None, Mock(username="newuser"))
77+
]
78+
79+
for existing_user, expected_status, expected_detail, create_result in register_cases:
80+
self._setup_request()
81+
mock_user_create = UserCreate(username="testuser", email="[email protected]", password="password")
82+
83+
self.mock_user_repo.get_user.return_value = existing_user
84+
self.mock_user_repo.create_user.side_effect = None
85+
86+
with patch('app.api.routes.auth.get_remote_address', return_value="127.0.0.1"):
87+
if expected_status:
88+
if expected_status == 500:
89+
self.mock_user_repo.create_user.side_effect = create_result
90+
with patch('app.api.routes.auth.security_service.get_password_hash', return_value="hashed"):
91+
with pytest.raises(HTTPException) as exc_info:
92+
await register(self.mock_request, mock_user_create, self.mock_user_repo)
93+
else:
94+
with pytest.raises(HTTPException) as exc_info:
95+
await register(self.mock_request, mock_user_create, self.mock_user_repo)
96+
97+
assert exc_info.value.status_code == expected_status
98+
assert exc_info.value.detail == expected_detail
99+
else:
100+
self.mock_user_repo.create_user.return_value = create_result
101+
with patch('app.api.routes.auth.security_service.get_password_hash', return_value="hashed"):
102+
with patch('app.api.routes.auth.UserResponse.model_validate') as mock_validate:
103+
mock_response = Mock()
104+
mock_validate.return_value = mock_response
105+
106+
result = await register(self.mock_request, mock_user_create, self.mock_user_repo)
107+
assert result == mock_response
108+
109+
self.mock_user_repo.reset_mock()
110+
111+
@pytest.mark.asyncio
112+
async def test_verify_token_scenarios(self) -> None:
113+
mock_current_user = Mock(username="testuser")
114+
115+
verify_cases = [
116+
(Mock(get=Mock(side_effect=Exception("Cookie error"))), 401, "Invalid token"),
117+
({"csrf_token": "csrf_value"}, None, "csrf_value"),
118+
({}, None, "")
119+
]
120+
121+
for cookies, expected_status, expected_csrf in verify_cases:
122+
self._setup_request()
123+
self.mock_request.cookies = cookies
124+
125+
with patch('app.api.routes.auth.get_remote_address', return_value="127.0.0.1"):
126+
if expected_status:
127+
with pytest.raises(HTTPException) as exc_info:
128+
await verify_token(self.mock_request, mock_current_user)
129+
130+
assert exc_info.value.status_code == expected_status
131+
assert exc_info.value.detail == "Invalid token"
132+
else:
133+
result = await verify_token(self.mock_request, mock_current_user)
134+
135+
assert result["valid"] is True
136+
assert result["username"] == "testuser"
137+
assert result["csrf_token"] == expected_csrf
138+
139+
@pytest.mark.asyncio
140+
async def test_logout_successful(self) -> None:
141+
self._setup_request()
142+
143+
with patch('app.api.routes.auth.get_remote_address', return_value="127.0.0.1"):
144+
result = await logout(self.mock_request, self.mock_response)
145+
146+
assert result["message"] == "Logout successful"
147+
assert self.mock_response.delete_cookie.call_count == 2
148+
149+
@pytest.mark.asyncio
150+
async def test_missing_user_agent_scenarios(self) -> None:
151+
endpoints = [
152+
("login", lambda: login(self.mock_request, self.mock_response,
153+
Mock(spec=OAuth2PasswordRequestForm, username="test", password="pass"),
154+
self.mock_user_repo)),
155+
("register", lambda: register(self.mock_request,
156+
UserCreate(username="test", email="[email protected]", password="pass"),
157+
self.mock_user_repo)),
158+
("verify_token", lambda: verify_token(self.mock_request, Mock(username="testuser"))),
159+
("logout", lambda: logout(self.mock_request, self.mock_response))
160+
]
161+
162+
for endpoint_name, endpoint_func in endpoints:
163+
self._setup_request(has_user_agent=False)
164+
165+
if endpoint_name == "register":
166+
self.mock_user_repo.get_user.return_value = None
167+
with patch('app.api.routes.auth.get_remote_address', return_value="127.0.0.1"):
168+
with patch('app.api.routes.auth.security_service.get_password_hash', return_value="hashed"):
169+
with patch('app.api.routes.auth.UserResponse.model_validate', return_value=Mock()):
170+
self.mock_user_repo.create_user.return_value = Mock(username="test")
171+
result = await endpoint_func()
172+
assert result is not None
173+
elif endpoint_name == "verify_token":
174+
self.mock_request.cookies = {"csrf_token": "csrf"}
175+
with patch('app.api.routes.auth.get_remote_address', return_value="127.0.0.1"):
176+
result = await endpoint_func()
177+
assert result["valid"] is True
178+
elif endpoint_name == "logout":
179+
with patch('app.api.routes.auth.get_remote_address', return_value="127.0.0.1"):
180+
result = await endpoint_func()
181+
assert result["message"] == "Logout successful"
182+
else:
183+
self.mock_user_repo.get_user.return_value = None
184+
with patch('app.api.routes.auth.get_remote_address', return_value="127.0.0.1"):
185+
with pytest.raises(HTTPException):
186+
await endpoint_func()
187+
188+
self.mock_user_repo.reset_mock()
189+
self.mock_response.reset_mock()

0 commit comments

Comments
 (0)