test: rename SearchSpace -> Workspace across tests + fixtures (Phase 2 Wave F)

Apply the same rename to surfsense_backend/tests: workspace_id fields/vars,
Workspace* classes/schemas, table name searchspaces -> workspaces in raw SQL,
and the API URL spellings -> /workspaces. Preserves the carve-out wire literals
tests assert (Celery task names, OTel key "search_space.id").
This commit is contained in:
CREDO23 2026-06-26 18:36:46 +02:00
parent 56826a63bc
commit ca9bd28934
124 changed files with 1269 additions and 1269 deletions

View file

@ -1,7 +1,7 @@
"""Behavior guard for the shared find/upsert/update logic (BaseNotificationHandler).
Uses the connector-indexing handler instance to drive the base methods against
real Postgres, pinning upsert dedup, search-space scoping, and status stamping.
real Postgres, pinning upsert dedup, workspace scoping, and status stamping.
"""
from __future__ import annotations
@ -10,7 +10,7 @@ import pytest
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import SearchSpace, User
from app.db import Workspace, User
from app.notifications.persistence import Notification
from app.notifications.service import NotificationService
@ -22,7 +22,7 @@ handler = NotificationService.connector_indexing
async def test_find_or_create_creates_with_progress_metadata(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""Creating a notification seeds operation id, in-progress status, and start time."""
notification = await handler.find_or_create_notification(
@ -31,7 +31,7 @@ async def test_find_or_create_creates_with_progress_metadata(
operation_id="op-create",
title="Title",
message="Message",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
assert notification.notification_metadata["operation_id"] == "op-create"
@ -42,7 +42,7 @@ async def test_find_or_create_creates_with_progress_metadata(
async def test_find_or_create_upserts_same_operation(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""Reusing an operation id updates the same row instead of creating a duplicate."""
first = await handler.find_or_create_notification(
@ -51,7 +51,7 @@ async def test_find_or_create_upserts_same_operation(
operation_id="op-upsert",
title="First",
message="First message",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
second = await handler.find_or_create_notification(
@ -60,7 +60,7 @@ async def test_find_or_create_upserts_same_operation(
operation_id="op-upsert",
title="Second",
message="Second message",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
assert second.id == first.id
@ -76,22 +76,22 @@ async def test_find_or_create_upserts_same_operation(
assert count == 1
async def test_find_by_operation_is_scoped_to_search_space(
async def test_find_by_operation_is_scoped_to_workspace(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""Operation-id lookup is scoped per search space, so other spaces don't match."""
"""Operation-id lookup is scoped per workspace, so other spaces don't match."""
await handler.find_or_create_notification(
session=db_session,
user_id=db_user.id,
operation_id="op-scoped",
title="Title",
message="Message",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
other_space = SearchSpace(name="Other Space", user_id=db_user.id)
other_space = Workspace(name="Other Space", user_id=db_user.id)
db_session.add(other_space)
await db_session.flush()
@ -99,7 +99,7 @@ async def test_find_by_operation_is_scoped_to_search_space(
session=db_session,
user_id=db_user.id,
operation_id="op-scoped",
search_space_id=other_space.id,
workspace_id=other_space.id,
)
assert found_other is None
@ -107,7 +107,7 @@ async def test_find_by_operation_is_scoped_to_search_space(
session=db_session,
user_id=db_user.id,
operation_id="op-scoped",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
assert found_same is not None
@ -115,7 +115,7 @@ async def test_find_by_operation_is_scoped_to_search_space(
async def test_update_notification_completed_stamps_completed_at(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""Completing a notification stamps completed_at and merges metadata updates."""
notification = await handler.find_or_create_notification(
@ -124,7 +124,7 @@ async def test_update_notification_completed_stamps_completed_at(
operation_id="op-complete",
title="Title",
message="Message",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
updated = await handler.update_notification(
@ -142,7 +142,7 @@ async def test_update_notification_completed_stamps_completed_at(
async def test_update_notification_failed_stamps_completed_at(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""Failing a notification also stamps completed_at for the terminal state."""
notification = await handler.find_or_create_notification(
@ -151,7 +151,7 @@ async def test_update_notification_failed_stamps_completed_at(
operation_id="op-fail",
title="Title",
message="Message",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
updated = await handler.update_notification(

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import SearchSpace, User
from app.db import Workspace, User
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration
@ -13,7 +13,7 @@ pytestmark = pytest.mark.integration
handler = NotificationService.comment_reply
async def _notify(db_session, db_user, db_search_space, *, reply_id=1, preview="hi"):
async def _notify(db_session, db_user, db_workspace, *, reply_id=1, preview="hi"):
"""Raise a comment-reply notification for the assertions in the tests below."""
return await handler.notify_comment_reply(
session=db_session,
@ -28,15 +28,15 @@ async def _notify(db_session, db_user, db_search_space, *, reply_id=1, preview="
author_avatar_url=None,
author_email="bob@surfsense.net",
content_preview=preview,
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
async def test_comment_reply_title_and_message(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""A reply notification names the author and carries the comment preview."""
notification = await _notify(db_session, db_user, db_search_space, preview="thanks")
notification = await _notify(db_session, db_user, db_workspace, preview="thanks")
assert notification.type == "comment_reply"
assert notification.title == "Bob replied in a thread"
@ -44,21 +44,21 @@ async def test_comment_reply_title_and_message(
async def test_comment_reply_truncates_long_preview(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""A long comment preview is truncated in the reply message."""
notification = await _notify(
db_session, db_user, db_search_space, preview="y" * 150
db_session, db_user, db_workspace, preview="y" * 150
)
assert notification.message == "y" * 100 + "..."
async def test_comment_reply_is_idempotent(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""Re-notifying the same reply id reuses the existing notification row."""
first = await _notify(db_session, db_user, db_search_space, reply_id=5)
second = await _notify(db_session, db_user, db_search_space, reply_id=5)
first = await _notify(db_session, db_user, db_workspace, reply_id=5)
second = await _notify(db_session, db_user, db_workspace, reply_id=5)
assert second.id == first.id

View file

@ -10,7 +10,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import SearchSpace, User
from app.db import Workspace, User
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration
@ -19,7 +19,7 @@ pytestmark = pytest.mark.integration
async def test_indexing_started_opens_notification(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""Starting indexing opens an unread notification with connecting-stage metadata."""
notification = await NotificationService.connector_indexing.notify_indexing_started(
@ -28,7 +28,7 @@ async def test_indexing_started_opens_notification(
connector_id=42,
connector_name="Notion - My Workspace",
connector_type="NOTION_CONNECTOR",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
assert notification.id is not None
@ -50,7 +50,7 @@ async def test_indexing_started_opens_notification(
async def _started(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
*,
connector_name: str = "Notion - My Workspace",
):
@ -61,17 +61,17 @@ async def _started(
connector_id=42,
connector_name=connector_name,
connector_type="NOTION_CONNECTOR",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
async def test_indexing_progress_reports_stage_and_percent(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""Progress updates surface the stage message and compute a percent complete."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
updated = await NotificationService.connector_indexing.notify_indexing_progress(
session=db_session,
@ -93,10 +93,10 @@ async def test_indexing_progress_reports_stage_and_percent(
async def test_indexing_completed_clean_success(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""A clean multi-file sync reports ready/completed with plural wording."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
done = await NotificationService.connector_indexing.notify_indexing_completed(
session=db_session,
@ -113,10 +113,10 @@ async def test_indexing_completed_clean_success(
async def test_indexing_completed_singular_file(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""A single synced file uses singular 'file' wording."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
done = await NotificationService.connector_indexing.notify_indexing_completed(
session=db_session,
@ -130,10 +130,10 @@ async def test_indexing_completed_singular_file(
async def test_indexing_completed_nothing_to_sync(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""Completing with nothing new reports 'Already up to date!'."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
done = await NotificationService.connector_indexing.notify_indexing_completed(
session=db_session,
@ -149,10 +149,10 @@ async def test_indexing_completed_nothing_to_sync(
async def test_indexing_completed_hard_failure(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""An error with nothing synced reports a hard failure."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
done = await NotificationService.connector_indexing.notify_indexing_completed(
session=db_session,
@ -170,10 +170,10 @@ async def test_indexing_completed_hard_failure(
async def test_indexing_completed_partial_with_error_note(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""An error after partial progress still completes, with an appended note."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
done = await NotificationService.connector_indexing.notify_indexing_completed(
session=db_session,
@ -190,10 +190,10 @@ async def test_indexing_completed_partial_with_error_note(
async def test_retry_progress_frames_delay_as_providers(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""A retry message frames the delay as the provider's, using its short name."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
retry = await NotificationService.connector_indexing.notify_retry_progress(
session=db_session,
@ -214,10 +214,10 @@ async def test_retry_progress_frames_delay_as_providers(
async def test_retry_progress_shows_wait_and_synced_count(
db_session: AsyncSession,
db_user: User,
db_search_space: SearchSpace,
db_workspace: Workspace,
):
"""A retry surfaces the wait time and how many items synced so far."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
retry = await NotificationService.connector_indexing.notify_retry_progress(
session=db_session,

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import SearchSpace, User
from app.db import Workspace, User
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration
@ -13,22 +13,22 @@ pytestmark = pytest.mark.integration
handler = NotificationService.document_processing
async def _started(db_session, db_user, db_search_space, *, name="report.pdf"):
async def _started(db_session, db_user, db_workspace, *, name="report.pdf"):
"""Open a document-processing notification to update in the tests below."""
return await handler.notify_processing_started(
session=db_session,
user_id=db_user.id,
document_type="FILE",
document_name=name,
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
async def test_processing_started_queues(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""Starting processing queues a notification in the 'queued' stage."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
assert notification.type == "document_processing"
assert notification.title == "Processing: report.pdf"
@ -37,10 +37,10 @@ async def test_processing_started_queues(
async def test_processing_progress_maps_stage(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""A progress update maps the stage to its user-facing message."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
updated = await handler.notify_processing_progress(
session=db_session, notification=notification, stage="parsing"
@ -51,10 +51,10 @@ async def test_processing_progress_maps_stage(
async def test_processing_completed_success(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""Successful processing reports ready/searchable and a completed status."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
done = await handler.notify_processing_completed(
session=db_session, notification=notification, document_id=99
@ -66,10 +66,10 @@ async def test_processing_completed_success(
async def test_processing_completed_failure(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""Failed processing reports a failed status with the error in the message."""
notification = await _started(db_session, db_user, db_search_space)
notification = await _started(db_session, db_user, db_workspace)
done = await handler.notify_processing_completed(
session=db_session, notification=notification, error_message="bad file"
@ -81,7 +81,7 @@ async def test_processing_completed_failure(
async def test_processing_started_truncates_long_filename(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""A long filename is truncated in the title but kept in metadata."""
long_name = "a" * 250
@ -91,7 +91,7 @@ async def test_processing_started_truncates_long_filename(
user_id=db_user.id,
document_type="FILE",
document_name=long_name,
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
assert len(notification.title) <= 200

View file

@ -28,14 +28,14 @@ async def _seed(
title: str = "Title",
message: str = "Message",
read: bool = False,
search_space_id: int | None = None,
workspace_id: int | None = None,
metadata: dict | None = None,
created_at: datetime | None = None,
) -> Notification:
"""Insert a notification row directly for the API tests to read back."""
notification = Notification(
user_id=user.id,
search_space_id=search_space_id,
workspace_id=workspace_id,
type=type,
title=title,
message=message,

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import SearchSpace, User
from app.db import Workspace, User
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration
@ -14,7 +14,7 @@ handler = NotificationService.insufficient_credits
async def test_insufficient_credits_message_and_action(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""An insufficient-credits notification states cost and carries a buy-credits link."""
notification = await handler.notify_insufficient_credits(
@ -22,7 +22,7 @@ async def test_insufficient_credits_message_and_action(
user_id=db_user.id,
document_name="short.pdf",
document_type="FILE",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
balance_micros=250_000,
required_micros=1_000_000,
)
@ -36,12 +36,12 @@ async def test_insufficient_credits_message_and_action(
assert notification.notification_metadata["status"] == "failed"
assert notification.notification_metadata["action_label"] == "Buy credits"
assert notification.notification_metadata["action_url"] == (
f"/dashboard/{db_search_space.id}/buy-more"
f"/dashboard/{db_workspace.id}/buy-more"
)
async def test_insufficient_credits_truncates_long_name(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""A long document name is truncated in the notification title."""
long_name = "a" * 50
@ -51,7 +51,7 @@ async def test_insufficient_credits_truncates_long_name(
user_id=db_user.id,
document_name=long_name,
document_type="FILE",
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
balance_micros=250_000,
required_micros=1_000_000,
)

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.db import SearchSpace, User
from app.db import Workspace, User
from app.notifications.service import NotificationService
pytestmark = pytest.mark.integration
@ -13,7 +13,7 @@ pytestmark = pytest.mark.integration
handler = NotificationService.mention
async def _notify(db_session, db_user, db_search_space, *, mention_id=1, preview="hi"):
async def _notify(db_session, db_user, db_workspace, *, mention_id=1, preview="hi"):
"""Raise an @mention notification for the assertions in the tests below."""
return await handler.notify_new_mention(
session=db_session,
@ -28,15 +28,15 @@ async def _notify(db_session, db_user, db_search_space, *, mention_id=1, preview
author_avatar_url=None,
author_email="alice@surfsense.net",
content_preview=preview,
search_space_id=db_search_space.id,
workspace_id=db_workspace.id,
)
async def test_new_mention_title_and_message(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""A mention notification names the author and carries the comment preview."""
notification = await _notify(db_session, db_user, db_search_space, preview="hello")
notification = await _notify(db_session, db_user, db_workspace, preview="hello")
assert notification.type == "new_mention"
assert notification.title == "Alice mentioned you"
@ -44,21 +44,21 @@ async def test_new_mention_title_and_message(
async def test_new_mention_truncates_long_preview(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""A long comment preview is truncated in the mention message."""
notification = await _notify(
db_session, db_user, db_search_space, preview="x" * 150
db_session, db_user, db_workspace, preview="x" * 150
)
assert notification.message == "x" * 100 + "..."
async def test_new_mention_is_idempotent(
db_session: AsyncSession, db_user: User, db_search_space: SearchSpace
db_session: AsyncSession, db_user: User, db_workspace: Workspace
):
"""Re-notifying the same mention id reuses the existing notification row."""
first = await _notify(db_session, db_user, db_search_space, mention_id=7)
second = await _notify(db_session, db_user, db_search_space, mention_id=7)
first = await _notify(db_session, db_user, db_workspace, mention_id=7)
second = await _notify(db_session, db_user, db_workspace, mention_id=7)
assert second.id == first.id