refactor: replace DefaultLimitOffsetPage with LimitOffsetPage in multiple files and update timezone handling to use UTC

This commit is contained in:
Abhimanyu Saharan
2026-02-09 20:40:17 +05:30
parent 1f105c19ab
commit 020d02fa22
51 changed files with 302 additions and 192 deletions

View File

@@ -4,16 +4,15 @@
from __future__ import annotations
from dataclasses import dataclass, field
from types import SimpleNamespace
from typing import TYPE_CHECKING, cast
from typing import Any
from uuid import uuid4
import pytest
from app.api import board_groups
if TYPE_CHECKING:
from sqlmodel.ext.asyncio.session import AsyncSession
from app.models.organization_members import OrganizationMember
from app.models.organizations import Organization
from app.services.organizations import OrganizationContext
@dataclass
@@ -47,12 +46,20 @@ async def test_delete_board_group_cleans_group_memory_first(
_fake_require_group_access,
)
session = _FakeSession()
ctx = SimpleNamespace(member=object())
session: Any = _FakeSession()
org_id = uuid4()
ctx = OrganizationContext(
organization=Organization(id=org_id, name=f"org-{org_id}"),
member=OrganizationMember(
organization_id=org_id,
user_id=uuid4(),
role="admin",
),
)
await board_groups.delete_board_group(
group_id=group_id,
session=cast("AsyncSession", session),
session=session,
ctx=ctx,
)

View File

@@ -4,7 +4,7 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, cast
from typing import Any
from uuid import uuid4
import pytest
@@ -12,9 +12,6 @@ import pytest
from app.api import boards
from app.models.boards import Board
if TYPE_CHECKING:
from sqlmodel.ext.asyncio.session import AsyncSession
_NO_EXEC_RESULTS_ERROR = "No more exec_results left for session.exec"
@@ -47,7 +44,7 @@ class _FakeSession:
@pytest.mark.asyncio
async def test_delete_board_cleans_org_board_access_rows() -> None:
"""Deleting a board should clear org-board access rows before commit."""
session = _FakeSession(exec_results=[[], []])
session: Any = _FakeSession(exec_results=[[], []])
board = Board(
id=uuid4(),
organization_id=uuid4(),
@@ -57,7 +54,7 @@ async def test_delete_board_cleans_org_board_access_rows() -> None:
)
await boards.delete_board(
session=cast("AsyncSession", session),
session=session,
board=board,
)

View File

@@ -50,7 +50,8 @@ async def test_get_session_rolls_back_on_dependency_error(monkeypatch: pytest.Mo
class _FakeDependencySession:
rollbacks: int = 0
def in_transaction(self) -> bool:
@staticmethod
def in_transaction() -> bool:
return True
async def rollback(self) -> None:
@@ -89,16 +90,19 @@ async def test_create_rolls_back_when_commit_fails() -> None:
def add(self, value: Any) -> None:
self.added.append(value)
async def flush(self) -> None:
@staticmethod
async def flush() -> None:
return None
async def commit(self) -> None:
@staticmethod
async def commit() -> None:
raise _CommitError("commit failed")
async def rollback(self) -> None:
self.rollback_calls += 1
async def refresh(self, _value: Any) -> None:
@staticmethod
async def refresh(_value: Any) -> None:
return None
session = _FailCommitSession()
@@ -124,7 +128,8 @@ async def test_delete_where_rolls_back_when_commit_fails() -> None:
self.exec_calls += 1
return SimpleNamespace(rowcount=3)
async def commit(self) -> None:
@staticmethod
async def commit() -> None:
raise _CommitError("commit failed")
async def rollback(self) -> None:

View File

@@ -4,17 +4,16 @@
from __future__ import annotations
from dataclasses import dataclass, field
from types import SimpleNamespace
from typing import TYPE_CHECKING, cast
from typing import Any
from uuid import uuid4
import pytest
from fastapi import HTTPException, status
from app.api import organizations
if TYPE_CHECKING:
from sqlmodel.ext.asyncio.session import AsyncSession
from app.models.organization_members import OrganizationMember
from app.models.organizations import Organization
from app.services.organizations import OrganizationContext
@dataclass
@@ -35,15 +34,19 @@ class _FakeSession:
@pytest.mark.asyncio
async def test_delete_my_org_cleans_dependents_before_organization_delete() -> None:
"""Delete flow should remove dependent rows before the organization row."""
session = _FakeSession()
session: Any = _FakeSession()
org_id = uuid4()
ctx = SimpleNamespace(
organization=SimpleNamespace(id=org_id),
member=SimpleNamespace(role="owner"),
ctx = OrganizationContext(
organization=Organization(id=org_id, name=f"org-{org_id}"),
member=OrganizationMember(
organization_id=org_id,
user_id=uuid4(),
role="owner",
),
)
await organizations.delete_my_org(
session=cast("AsyncSession", session),
session=session,
ctx=ctx,
)
@@ -77,15 +80,20 @@ async def test_delete_my_org_cleans_dependents_before_organization_delete() -> N
@pytest.mark.asyncio
async def test_delete_my_org_requires_owner_role() -> None:
"""Delete flow should reject non-owner members with HTTP 403."""
session = _FakeSession()
ctx = SimpleNamespace(
organization=SimpleNamespace(id=uuid4()),
member=SimpleNamespace(role="admin"),
session: Any = _FakeSession()
org_id = uuid4()
ctx = OrganizationContext(
organization=Organization(id=org_id, name=f"org-{org_id}"),
member=OrganizationMember(
organization_id=org_id,
user_id=uuid4(),
role="admin",
),
)
with pytest.raises(HTTPException) as exc_info:
await organizations.delete_my_org(
session=cast("AsyncSession", session),
session=session,
ctx=ctx,
)

View File

@@ -3,16 +3,17 @@
from __future__ import annotations
from dataclasses import dataclass, field
from types import SimpleNamespace
from typing import Any
from uuid import uuid4
from uuid import UUID, uuid4
import pytest
from fastapi import HTTPException, status
from app.api import organizations
from app.models.organization_members import OrganizationMember
from app.models.organizations import Organization
from app.models.users import User
from app.services.organizations import OrganizationContext
@dataclass
@@ -58,6 +59,17 @@ class _FakeSession:
self.committed += 1
def _make_ctx(*, org_id: UUID, user_id: UUID, role: str) -> OrganizationContext:
return OrganizationContext(
organization=Organization(id=org_id, name=f"org-{org_id}"),
member=OrganizationMember(
organization_id=org_id,
user_id=user_id,
role=role,
),
)
@pytest.mark.asyncio
async def test_remove_org_member_deletes_member_access_and_member() -> None:
org_id = uuid4()
@@ -83,10 +95,7 @@ async def test_remove_org_member_deletes_member_access_and_member() -> None:
_FakeExecResult(first_value=fallback_org_id),
],
)
ctx = SimpleNamespace(
organization=SimpleNamespace(id=org_id),
member=SimpleNamespace(user_id=actor_user_id, role="admin"),
)
ctx = _make_ctx(org_id=org_id, user_id=actor_user_id, role="admin")
await organizations.remove_org_member(member_id=member_id, session=session, ctx=ctx)
@@ -109,10 +118,7 @@ async def test_remove_org_member_disallows_self_removal() -> None:
role="member",
)
session = _FakeSession(exec_results=[_FakeExecResult(first_value=member)])
ctx = SimpleNamespace(
organization=SimpleNamespace(id=org_id),
member=SimpleNamespace(user_id=user_id, role="owner"),
)
ctx = _make_ctx(org_id=org_id, user_id=user_id, role="owner")
with pytest.raises(HTTPException) as exc_info:
await organizations.remove_org_member(member_id=member.id, session=session, ctx=ctx)
@@ -133,10 +139,7 @@ async def test_remove_org_member_requires_owner_to_remove_owner() -> None:
role="owner",
)
session = _FakeSession(exec_results=[_FakeExecResult(first_value=member)])
ctx = SimpleNamespace(
organization=SimpleNamespace(id=org_id),
member=SimpleNamespace(user_id=uuid4(), role="admin"),
)
ctx = _make_ctx(org_id=org_id, user_id=uuid4(), role="admin")
with pytest.raises(HTTPException) as exc_info:
await organizations.remove_org_member(member_id=member.id, session=session, ctx=ctx)
@@ -162,10 +165,7 @@ async def test_remove_org_member_rejects_removing_last_owner() -> None:
_FakeExecResult(all_values=[member]),
],
)
ctx = SimpleNamespace(
organization=SimpleNamespace(id=org_id),
member=SimpleNamespace(user_id=uuid4(), role="owner"),
)
ctx = _make_ctx(org_id=org_id, user_id=uuid4(), role="owner")
with pytest.raises(HTTPException) as exc_info:
await organizations.remove_org_member(member_id=member.id, session=session, ctx=ctx)

View File

@@ -17,8 +17,8 @@ async def test_request_id_middleware_passes_through_non_http_scope() -> None:
middleware = RequestIdMiddleware(app)
scope = {"type": "websocket", "headers": []}
await middleware(scope, lambda: None, lambda message: None) # type: ignore[arg-type]
request_scope = {"type": "websocket", "headers": []}
await middleware(request_scope, lambda: None, lambda message: None) # type: ignore[arg-type]
assert called is True
@@ -40,11 +40,11 @@ async def test_request_id_middleware_ignores_blank_client_header_and_generates_o
middleware = RequestIdMiddleware(app)
scope = {
request_scope = {
"type": "http",
"headers": [(REQUEST_ID_HEADER.lower().encode("latin-1"), b" ")],
}
await middleware(scope, lambda: None, send)
await middleware(request_scope, lambda: None, send)
assert isinstance(captured_request_id, str) and captured_request_id
# Header should reflect the generated id, not the blank one.
@@ -78,8 +78,8 @@ async def test_request_id_middleware_does_not_duplicate_existing_header() -> Non
middleware = RequestIdMiddleware(app)
scope = {"type": "http", "headers": []}
await middleware(scope, lambda: None, send)
request_scope = {"type": "http", "headers": []}
await middleware(request_scope, lambda: None, send)
assert sent_start is True
assert start_headers is not None

View File

@@ -20,7 +20,7 @@ from app.services import task_dependencies as td
async def _make_engine() -> AsyncEngine:
# Single shared in-memory db per engine.
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
async with engine.begin() as conn:
async with engine.connect() as conn, conn.begin():
await conn.run_sync(SQLModel.metadata.create_all)
return engine