Skip to content

Commit 3d3024a

Browse files
Merge pull request #30 from codewithme-py/feat/tests-coverage
test: add comprehensive test suite and improve service robustness and model defaults
2 parents 3b921a7 + ea89490 commit 3d3024a

56 files changed

Lines changed: 3877 additions & 113 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
run: uv run ruff check .
6262

6363
- name: Run tests
64-
run: uv run pytest # --cov-fail-under=70 Temporarily disabled
64+
run: uv run pytest --cov-fail-under=95
6565

6666
- name: Run mypy
6767
run: uv run mypy .
File renamed without changes.

app/core/admin/admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def docs_link_formatter(model: Any, name: Any) -> Any:
9090
return Markup(', '.join(links))
9191

9292

93-
class VerificationRequestAdmin(ModelView, model=VerificationRequest):
93+
class VerificationRequestAdmin(AdminAccessMixin, model=VerificationRequest):
9494
column_list = [
9595
VerificationRequest.id,
9696
VerificationRequest.user_id,

app/core/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,16 @@ class Settings(BaseSettings):
3939
max_file_size_bytes: int = Field(alias='MAX_FILE_SIZE_BYTES')
4040
secret_key: str = Field(alias='SECRET_KEY')
4141
debug_mode: bool = Field(default=False, alias='DEBUG_MODE')
42+
unverified_seller_limit: int = 3
43+
verified_seller_limit: int = 100
4244

4345
@computed_field
4446
def database_url(self) -> str:
4547
return f'postgresql+asyncpg://{self.db_user}:{self.db_password}@{self.db_host}:{self.db_port}/{self.db_name}'
4648

49+
@computed_field
50+
def database_url_masked(self) -> str:
51+
return f'postgresql+asyncpg://{self.db_user}:****@{self.db_host}:{self.db_port}/{self.db_name}'
52+
4753

4854
settings = Settings()

app/core/exception_handlers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
InsufficientInventoryError,
88
NotFoundError,
99
PermissionDeniedError,
10+
SellerLimitExceededError,
1011
UserAlreadyExists,
1112
VerificationRequestAlreadyExists,
1213
)
@@ -76,3 +77,12 @@ async def verification_request_already_exists_handler(
7677
status_code=status.HTTP_400_BAD_REQUEST,
7778
content={'detail': str(exc) or 'Verification request already exists'},
7879
)
80+
81+
82+
async def seller_limit_exceeded_handler(
83+
request: Request, exc: SellerLimitExceededError
84+
) -> JSONResponse:
85+
return JSONResponse(
86+
status_code=status.HTTP_400_BAD_REQUEST,
87+
content={'detail': str(exc) or 'Seller limit exceeded'},
88+
)

app/core/exceptions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ class UserAlreadyExists(AppError):
1111
"""User with such email already exists."""
1212

1313

14+
class SellerLimitExceededError(AppError):
15+
"""Seller product listing limit exceeded."""
16+
17+
def __init__(self, message: str = 'Seller product listing limit exceeded'):
18+
super().__init__(message=message)
19+
20+
1421
class CredentialsError(AppError):
1522
"""Invalid credentials."""
1623

app/core/s3.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import logging
2-
from collections.abc import AsyncGenerator
1+
from collections.abc import AsyncIterator
32
from contextlib import asynccontextmanager
43
from typing import Any
54

65
import aioboto3 # type: ignore
6+
import structlog
77
from botocore.exceptions import ClientError
88

99
from app.core.config import settings
1010

11-
logger = logging.getLogger(__name__)
11+
logger = structlog.get_logger(__name__)
1212

1313
session = aioboto3.Session()
1414

1515

1616
@asynccontextmanager
17-
async def get_s3_client() -> AsyncGenerator[Any, None]:
17+
async def get_s3_client() -> AsyncIterator[Any]:
1818
async with session.client(
1919
's3',
2020
endpoint_url=settings.minio_url,

app/core/security.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,12 @@ async def check_permission(
6161
def check_ownership(user: User, obj: Any) -> None:
6262
if user.role in (UserRole.ADMIN, UserRole.MODERATOR):
6363
return
64-
if not hasattr(obj, 'owner_id'):
65-
raise ValueError(f'Object {type(obj)} does not have owner_id')
66-
if obj.owner_id != user.id:
64+
owner_attr = 'owner_id'
65+
if not hasattr(obj, 'owner_id') and hasattr(obj, 'user_id'):
66+
owner_attr = 'user_id'
67+
if not hasattr(obj, owner_attr):
68+
raise ValueError(f'Object {type(obj)} does not have owner_id or user_id')
69+
if getattr(obj, owner_attr) != user.id:
6770
raise PermissionDeniedError
6871

6972

app/core/setup.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
insufficient_inventory_error_handler,
77
not_found_error_handler,
88
permission_denied_handler,
9+
seller_limit_exceeded_handler,
910
user_already_exists_handler,
1011
verification_request_already_exists_handler,
1112
)
@@ -15,6 +16,7 @@
1516
InsufficientInventoryError,
1617
NotFoundError,
1718
PermissionDeniedError,
19+
SellerLimitExceededError,
1820
UserAlreadyExists,
1921
VerificationRequestAlreadyExists,
2022
)
@@ -37,3 +39,7 @@ def setup_exception_handlers(app: FastAPI) -> None:
3739
VerificationRequestAlreadyExists,
3840
verification_request_already_exists_handler, # type: ignore[arg-type]
3941
)
42+
app.add_exception_handler(
43+
SellerLimitExceededError,
44+
seller_limit_exceeded_handler, # type: ignore[arg-type]
45+
)

app/services/inventory/routes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
SELLER_DEPENDENCY = Depends(
3131
RoleChecker(
3232
allowed_roles=[UserRole.SELLER, UserRole.SELLER_B2B],
33-
required_verified=True,
33+
required_verified=False,
3434
)
3535
)
3636
ADMIN_DEPENDENCY = Depends(

0 commit comments

Comments
 (0)