Files
Jyotisha/tests/test_session_management_entrypoints.py
T
jesse-ux d2cec17995
Independent Staging Quality Gate / validate (push) Successful in 11m24s
Independent Staging Quality Gate / publish (push) Successful in 3m45s
test: stop requiring retired archive entrypoints in Python contracts
Flip archive tokens to must-not-appear on the session row and session hook only. Keep writeChatSession POST/PATCH and assert delete still uses DELETE. Grep tests/ before deleting frontend symbols.
2026-09-21 19:20:45 +08:00

95 lines
3.8 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" / "chart-library-panel.tsx",
_FRONTEND_SRC / "components" / "starter-home.tsx",
)
OPTIONAL_HOME_HOOKS = (
_FRONTEND_SRC / "hooks" / "use-session-management.ts",
_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