Skip to content

Commit bd0bd77

Browse files
authored
Refine hosted OAuth chat session flow (#1936)
1 parent ec664a9 commit bd0bd77

15 files changed

Lines changed: 904 additions & 628 deletions

File tree

mcpjam-inspector/client/src/App.tsx

Lines changed: 109 additions & 76 deletions
Large diffs are not rendered by default.

mcpjam-inspector/client/src/__tests__/App.hosted-oauth.test.tsx

Lines changed: 170 additions & 71 deletions
Large diffs are not rendered by default.

mcpjam-inspector/client/src/components/ChatTabV2.tsx

Lines changed: 98 additions & 97 deletions
Large diffs are not rendered by default.

mcpjam-inspector/client/src/components/__tests__/ChatTabV2.history-sync.test.tsx

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,7 @@ const chatSessionOnResetRef = vi.hoisted(() => ({
1515
current: undefined as undefined | ((reason?: string) => void),
1616
}));
1717
const lastUseChatSessionOptionsRef = vi.hoisted(() => ({
18-
current: undefined as
19-
| undefined
20-
| {
21-
directVisibility?: "private" | "workspace";
22-
onReset?: (reason?: string) => void;
23-
},
18+
current: undefined as any,
2419
}));
2520
const mockHistorySession = vi.hoisted(() => ({
2621
_id: "history-1",
@@ -187,10 +182,9 @@ vi.mock("@/components/chat-v2/error", () => ({
187182
}));
188183

189184
vi.mock("@/components/chat-v2/shared/chat-helpers", async (importOriginal) => {
190-
const actual =
191-
await importOriginal<
192-
typeof import("@/components/chat-v2/shared/chat-helpers")
193-
>();
185+
const actual = await importOriginal<
186+
typeof import("@/components/chat-v2/shared/chat-helpers")
187+
>();
194188
return {
195189
...actual,
196190
STARTER_PROMPTS: [],
@@ -375,10 +369,7 @@ const mockUseChatSession = {
375369
} as any;
376370

377371
vi.mock("@/hooks/use-chat-session", () => ({
378-
useChatSession: (options: {
379-
directVisibility?: "private" | "workspace";
380-
onReset?: (reason?: string) => void;
381-
}) => {
372+
useChatSession: (options: any) => {
382373
chatSessionOnResetRef.current = options.onReset;
383374
lastUseChatSessionOptionsRef.current = options;
384375

@@ -426,7 +417,7 @@ describe("ChatTabV2 history sync", () => {
426417
mockUseFeatureFlagEnabled.mockReturnValue(true);
427418
vi.stubGlobal(
428419
"confirm",
429-
vi.fn(() => true),
420+
vi.fn(() => true)
430421
);
431422
chatSessionOnResetRef.current = undefined;
432423
lastUseChatSessionOptionsRef.current = undefined;
@@ -470,6 +461,24 @@ describe("ChatTabV2 history sync", () => {
470461
vi.useRealTimers();
471462
});
472463

464+
it("suppresses hosted OAuth token fallback for chatbox contexts", () => {
465+
render(
466+
<ChatTabV2
467+
{...defaultProps}
468+
hostedWorkspaceIdOverride="workspace-1"
469+
hostedSelectedServerIdsOverride={["server-1"]}
470+
hostedContext={{ chatboxToken: "chatbox-token" }}
471+
/>
472+
);
473+
474+
expect(lastUseChatSessionOptionsRef.current?.hostedContext).toMatchObject({
475+
chatboxToken: "chatbox-token",
476+
});
477+
expect(
478+
lastUseChatSessionOptionsRef.current?.hostedContext?.oauthTokens
479+
).toBeUndefined();
480+
});
481+
473482
it("does not auto-reconnect workspace chat when oauth is required", async () => {
474483
const onReconnectServer = vi.fn().mockResolvedValue(undefined);
475484
mockUseChatSession.error = new Error(
@@ -480,14 +489,11 @@ describe("ChatTabV2 history sync", () => {
480489
serverName: "server-1",
481490
serverUrl: "https://server-1.example.com/mcp",
482491
},
483-
}),
492+
})
484493
);
485494

486495
render(
487-
<ChatTabV2
488-
{...defaultProps}
489-
onReconnectServer={onReconnectServer}
490-
/>,
496+
<ChatTabV2 {...defaultProps} onReconnectServer={onReconnectServer} />
491497
);
492498

493499
await flushMicrotasks();
@@ -512,7 +518,7 @@ describe("ChatTabV2 history sync", () => {
512518

513519
expect(mockGetChatHistoryDetail).not.toHaveBeenCalled();
514520
expect(screen.getByRole("textbox", { name: "Chat input" })).toHaveValue(
515-
"Unsaved draft",
521+
"Unsaved draft"
516522
);
517523
});
518524

@@ -525,7 +531,7 @@ describe("ChatTabV2 history sync", () => {
525531

526532
fireEvent.click(screen.getByRole("button", { name: "Show sessions" }));
527533
fireEvent.click(
528-
screen.getByRole("button", { name: "New personal thread" }),
534+
screen.getByRole("button", { name: "New personal thread" })
529535
);
530536
await flushMicrotasks();
531537

@@ -535,7 +541,7 @@ describe("ChatTabV2 history sync", () => {
535541

536542
expect(mockUseChatSession.resetChat).not.toHaveBeenCalled();
537543
expect(screen.getByRole("textbox", { name: "Chat input" })).toHaveValue(
538-
"Unsaved draft",
544+
"Unsaved draft"
539545
);
540546
});
541547

@@ -559,7 +565,7 @@ describe("ChatTabV2 history sync", () => {
559565
expect(screen.getByLabelText("Loading chat")).toBeInTheDocument();
560566

561567
fireEvent.click(
562-
screen.getByRole("button", { name: "New personal thread" }),
568+
screen.getByRole("button", { name: "New personal thread" })
563569
);
564570
await flushMicrotasks();
565571

@@ -612,7 +618,7 @@ describe("ChatTabV2 history sync", () => {
612618
expect(mockUseChatSession.loadChatSession).toHaveBeenCalledTimes(1);
613619
expect(screen.getByTestId("history-rail")).toHaveAttribute(
614620
"data-active-session-id",
615-
"history-1",
621+
"history-1"
616622
);
617623

618624
mockUseChatSession.status = "submitted";
@@ -629,7 +635,7 @@ describe("ChatTabV2 history sync", () => {
629635
expect(mockGetChatHistoryDetail).toHaveBeenCalledTimes(4);
630636
expect(screen.getByTestId("history-rail")).toHaveAttribute(
631637
"data-active-session-id",
632-
"none",
638+
"none"
633639
);
634640

635641
expect(mockUseChatSession.startChatWithMessages).toHaveBeenCalledWith(
@@ -651,11 +657,11 @@ describe("ChatTabV2 history sync", () => {
651657
uiType: "mcp-apps",
652658
},
653659
},
654-
},
660+
}
655661
);
656662
expect(mockUseChatSession.syncResumedVersion).toHaveBeenCalledWith(null);
657663
expect(mockToastError).toHaveBeenCalledWith(
658-
"This chat changed elsewhere. This reply stayed local, and your next send will continue in a new thread.",
664+
"This chat changed elsewhere. This reply stayed local, and your next send will continue in a new thread."
659665
);
660666
});
661667

@@ -682,19 +688,19 @@ describe("ChatTabV2 history sync", () => {
682688

683689
expect(screen.getByTestId("history-rail")).toHaveAttribute(
684690
"data-active-session-id",
685-
"history-1",
691+
"history-1"
686692
);
687693

688694
view.rerender(<ChatTabV2 {...defaultProps} selectedServerNames={[]} />);
689695
await flushMicrotasks();
690696

691697
expect(screen.getByTestId("history-rail")).toHaveAttribute(
692698
"data-active-session-id",
693-
"history-1",
699+
"history-1"
694700
);
695701
expect(mockUseChatSession.startChatWithMessages).not.toHaveBeenCalled();
696702
expect(mockUseChatSession.syncResumedVersion).not.toHaveBeenCalledWith(
697-
null,
703+
null
698704
);
699705
expect(mockToastError).not.toHaveBeenCalled();
700706
});
@@ -707,26 +713,30 @@ describe("ChatTabV2 history sync", () => {
707713
await flushMicrotasks();
708714

709715
expect(lastUseChatSessionOptionsRef.current?.directVisibility).toBe(
710-
"workspace",
716+
"workspace"
711717
);
712718
expect(mockGetChatHistoryDetail).not.toHaveBeenCalled();
713719
});
714720

715721
it("keeps the history rail visible while hiding shared-thread affordances when the flag is off", async () => {
716722
mockUseFeatureFlagEnabled.mockImplementation((flag: string) =>
717-
flag === "shared-threads-enabled" ? false : true,
723+
flag === "shared-threads-enabled" ? false : true
718724
);
719725

720726
render(<ChatTabV2 {...defaultProps} />);
721727

722728
fireEvent.click(screen.getByRole("button", { name: "Show sessions" }));
723729

724730
expect(screen.getByTestId("history-rail")).toBeInTheDocument();
725-
expect(screen.getByRole("button", { name: "New personal thread" })).toBeInTheDocument();
726-
expect(screen.queryByRole("button", { name: "New shared thread" })).not.toBeInTheDocument();
731+
expect(
732+
screen.getByRole("button", { name: "New personal thread" })
733+
).toBeInTheDocument();
734+
expect(
735+
screen.queryByRole("button", { name: "New shared thread" })
736+
).not.toBeInTheDocument();
727737
expect(screen.getByTestId("history-rail")).toHaveAttribute(
728738
"data-shared-threads-enabled",
729-
"false",
739+
"false"
730740
);
731741
});
732742

@@ -771,7 +781,7 @@ describe("ChatTabV2 history sync", () => {
771781

772782
expect(mockUseChatSession.loadChatSession).toHaveBeenCalledTimes(1);
773783
expect(screen.getByRole("textbox", { name: "Chat input" })).toHaveValue(
774-
"Local draft reply",
784+
"Local draft reply"
775785
);
776786
});
777787

@@ -788,11 +798,11 @@ describe("ChatTabV2 history sync", () => {
788798
await flushMicrotasks();
789799

790800
expect(screen.getByRole("textbox", { name: "Chat input" })).toHaveValue(
791-
"Unsaved local draft",
801+
"Unsaved local draft"
792802
);
793803
expect(mockChatHistoryAction).not.toHaveBeenCalledWith(
794804
"archive",
795-
"history-1",
805+
"history-1"
796806
);
797807
});
798808
});

0 commit comments

Comments
 (0)