refactor: replace DefaultLimitOffsetPage with LimitOffsetPage in multiple files and update timezone handling to use UTC
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user