Files
Jyotisha/tests/test_session_management_entrypoints.py
T
Jesse_ChenandClaude Opus 5.5 0f0fea325e
Independent Staging Quality Gate / validate (push) Failing after 15m9s
Independent Staging Quality Gate / publish (push) Skipped
Sidebar session menu and drag on every page (issue #116)
Rename, delete with confirmation, pin, share and reorder moved from Home's
session hook into the shared (app) shell, so /chart, /ephemeris, /reports
and /people rows get the same menu and drag as /. Home only adds its paging
cursor, what the open conversation does when deleted, and which local chats
are unsaved. BUG-1467.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
2026-10-28 01:52:47 +08:00

98 lines
4.1 KiB
Python

from pathlib import Path
PAGE = Path("frontend/src/app/(app)/page.tsx")
_FRONTEND_SRC = Path("frontend/src")
HOME_SURFACE_FILES = (
PAGE,
_FRONTEND_SRC / "lib" / "home-types.ts",
_FRONTEND_SRC / "lib" / "home-profile.ts",
_FRONTEND_SRC / "lib" / "home-cloud-sync.ts",
_FRONTEND_SRC / "components" / "birth-location-fields.tsx",
_FRONTEND_SRC / "components" / "profile-fields.tsx",
_FRONTEND_SRC / "components" / "onboarding-chat-message.tsx",
_FRONTEND_SRC / "components" / "people" / "people-page.tsx",
_FRONTEND_SRC / "components" / "starter-home.tsx",
)
OPTIONAL_HOME_HOOKS = (
_FRONTEND_SRC / "hooks" / "use-session-management.ts",
# issue #116:会话行的重命名、删除、收藏、转发、排序和删除确认框搬进共用外壳。
_FRONTEND_SRC / "hooks" / "use-shell-session-actions.ts",
_FRONTEND_SRC / "components" / "session-delete-dialog.tsx",
_FRONTEND_SRC / "hooks" / "use-consultation-run.ts",
_FRONTEND_SRC / "hooks" / "use-profile-onboarding.ts",
_FRONTEND_SRC / "hooks" / "use-rectification-surface.ts",
)
STYLES = Path("frontend/src/app/globals.css")
SESSION_ROW = Path("frontend/src/components/sidebar-session-row.tsx")
SESSION_DELETE_ROUTE = Path("frontend/src/app/api/sessions/[id]/route.ts")
SESSION_DELETE_MIGRATION = Path("frontend/supabase/migrations/20260721100000_chat_sessions_delete_grant.sql")
SESSION_WRITE_CONTRACT = Path("frontend/src/lib/chat-session-write-contract.ts")
def _home_surface() -> str:
parts = [path.read_text(encoding="utf-8") for path in HOME_SURFACE_FILES]
parts.extend(path.read_text(encoding="utf-8") for path in OPTIONAL_HOME_HOOKS if path.exists())
return "".join(parts)
def test_chat_history_management_actions_are_exposed() -> None:
source = _home_surface() + STYLES.read_text(encoding="utf-8") + SESSION_ROW.read_text(encoding="utf-8")
for expected in (
"renameSession",
"deleteSession",
"pendingSessionDeletion",
"确认删除",
"session-delete-overlay",
"togglePinnedSession",
"shareSession",
"share_payload_version",
"messages.map",
"onContextMenu",
"session-menu-trigger",
"Escape",
"focus-within",
"收藏",
"重命名",
"删除",
"转发",
'fetch(`/api/sessions/${encodeURIComponent(session.id)}`',
):
assert expected in source
row = SESSION_ROW.read_text(encoding="utf-8")
management = (_FRONTEND_SRC / "hooks" / "use-session-management.ts").read_text(encoding="utf-8")
for retired in (
"toggleArchivedSession",
"showArchivedSessions",
"已归档,可在左侧归档中恢复。",
"归档",
"恢复",
):
assert retired not in row
assert retired not in management
def test_chat_session_delete_is_server_controlled_and_granted() -> None:
route = SESSION_DELETE_ROUTE.read_text(encoding="utf-8")
migration = SESSION_DELETE_MIGRATION.read_text(encoding="utf-8")
assert 'from("chat_sessions")' in route
assert '.eq("user_id", user.id)' in route
assert 'count !== 1' in route
assert 'grant delete on table public.chat_sessions to authenticated' in migration.lower()
assert 'create policy chat_sessions_delete_own' in migration.lower()
assert 'using ((select auth.uid()) = user_id)' in migration.lower()
def test_session_archive_is_retired_and_write_helper_still_patches() -> None:
source = _home_surface()
write_contract = SESSION_WRITE_CONTRACT.read_text(encoding="utf-8")
assert "function toggleArchivedSession" not in source
assert "toggleArchivedView" not in source
assert 'writeChatSession(sessionId, { archived_at: nextArchivedAt }, "update")' not in source
assert "archived_at: nextArchivedAt" not in source
assert 'method: mode === "create" ? "POST" : "PATCH"' in write_contract
start = source.index("async function deleteSession")
end = source.index("function togglePinnedSession", start)
delete_action = source[start:end]
assert 'method: "DELETE"' in delete_action