diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx index 518992188..4ed72dc42 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx @@ -28,9 +28,10 @@ vi.mock("next-intl", () => { }); let seqParamValue: string | null = null; +let sessionIdParamValue = "0123456789abcdef"; vi.mock("next/navigation", () => { return { - useParams: () => ({ sessionId: "0123456789abcdef" }), + useParams: () => ({ sessionId: sessionIdParamValue }), useSearchParams: () => ({ get: (key: string) => { if (key !== "seq") return null; @@ -57,7 +58,7 @@ vi.mock("@/i18n/routing", () => { const getSessionDetailsMock = vi.fn(); const terminateActiveSessionMock = vi.fn(); -vi.mock("@/actions/active-sessions", () => { +vi.mock("@/lib/api-client/v1/actions/active-sessions", () => { return { getSessionDetails: (...args: unknown[]) => getSessionDetailsMock(...args), terminateActiveSession: (...args: unknown[]) => terminateActiveSessionMock(...args), @@ -267,9 +268,30 @@ afterEach(() => { routerBackMock.mockReset(); vi.useRealTimers(); seqParamValue = null; + sessionIdParamValue = "0123456789abcdef"; }); describe("SessionMessagesClient (request export actions)", () => { + test("decodes an URL-encoded canonical Session ID before loading details", async () => { + sessionIdParamValue = "pfx%3A9d403aeabe1f236d%3A1ee9a5d1bd4d98ce4bed39daca4b943e"; + getSessionDetailsMock.mockResolvedValue({ + ok: true, + data: buildDetailsData(), + }); + + const { unmount } = renderClient(); + await flushEffects(); + + expect(getSessionDetailsMock).toHaveBeenCalledWith( + "pfx:9d403aeabe1f236d:1ee9a5d1bd4d98ce4bed39daca4b943e", + undefined, + undefined, + undefined + ); + + unmount(); + }); + test("selected seq in URL overrides currentSequence for request export", async () => { seqParamValue = "3"; getSessionDetailsMock.mockResolvedValue({ diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx index 6a0d96638..22e3c18a3 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx @@ -53,15 +53,24 @@ import { SessionMessagesDetailsTabs } from "./session-details-tabs"; import { hasSnapshotData } from "./session-messages-guards"; import { SessionStats } from "./session-stats"; +function normalizeCanonicalSessionRouteParam(sessionId: string): string { + try { + const decoded = decodeURIComponent(sessionId); + return decoded.startsWith("pfx:") || decoded.startsWith("sid:") ? decoded : sessionId; + } catch { + return sessionId; + } +} + export function SessionMessagesClient() { const t = useTranslations("dashboard.sessions"); const tErrors = useTranslations("errors"); - const params = useParams(); + const params = useParams<{ sessionId: string }>(); const searchParams = useSearchParams(); const router = useRouter(); const pathname = usePathname(); - const sessionId = params.sessionId as string; + const sessionId = normalizeCanonicalSessionRouteParam(params.sessionId); // URL state const seqParam = searchParams.get("seq");