Merge branch 'v2/main' into v2/chore/2148-web-tab-smokes
This commit is contained in:
commit
11a1bdf6a6
5 changed files with 3008 additions and 1237 deletions
File diff suppressed because it is too large
Load diff
1474
clients/web/src/hooks/useOAuthRecovery.test.tsx
Normal file
1474
clients/web/src/hooks/useOAuthRecovery.test.tsx
Normal file
File diff suppressed because it is too large
Load diff
1382
clients/web/src/hooks/useOAuthRecovery.ts
Normal file
1382
clients/web/src/hooks/useOAuthRecovery.ts
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -2,8 +2,6 @@ import { describe, it, expect } from "vitest";
|
|||
import { InspectorClient } from "@inspector/core/mcp/index.js";
|
||||
import type { ServerEntry } from "@inspector/core/mcp/types.js";
|
||||
import { renderWithMantine } from "../test/renderWithMantine";
|
||||
import type { PendingReauth } from "../utils/pendingReauth";
|
||||
import type { PendingStepUp } from "../utils/stepUp";
|
||||
import {
|
||||
useSessionRef,
|
||||
type SessionRef,
|
||||
|
|
@ -27,27 +25,10 @@ const entry = (id: string, name = id): ServerEntry => ({
|
|||
const client = (): InspectorClient =>
|
||||
Object.create(InspectorClient.prototype) as InspectorClient;
|
||||
|
||||
const stepUp = (serverId: string): PendingStepUp => ({
|
||||
serverId,
|
||||
challenge: { reason: "unauthorized" },
|
||||
authorizationUrl: new URL("https://as.example/authorize"),
|
||||
source: "tool",
|
||||
});
|
||||
|
||||
const reauth = (serverId: string): PendingReauth => ({
|
||||
serverId,
|
||||
challenge: { reason: "unauthorized" },
|
||||
authorizationUrl: new URL("https://as.example/authorize"),
|
||||
authKind: "reauth",
|
||||
source: "tool",
|
||||
});
|
||||
|
||||
const values = (over: Partial<SessionValues> = {}): SessionValues => ({
|
||||
activeServerId: undefined,
|
||||
servers: [],
|
||||
inspectorClient: null,
|
||||
pendingStepUp: null,
|
||||
pendingReauth: null,
|
||||
...over,
|
||||
});
|
||||
|
||||
|
|
@ -84,6 +65,8 @@ describe("useSessionRef", () => {
|
|||
expect(snapshot.activeServerId).toBe("a");
|
||||
expect(snapshot.servers).toBe(servers);
|
||||
expect(snapshot.inspectorClient).toBe(c);
|
||||
// Seeded null and left alone: the two pending-OAuth slots are written by
|
||||
// their owner, `useOAuthRecovery`, not supplied to this hook (#2153).
|
||||
expect(snapshot.pendingStepUp).toBeNull();
|
||||
expect(snapshot.pendingReauth).toBeNull();
|
||||
});
|
||||
|
|
@ -99,34 +82,39 @@ describe("useSessionRef", () => {
|
|||
const h = harness(values());
|
||||
const servers = [entry("a"), entry("b")];
|
||||
const c = client();
|
||||
const up = stepUp("a");
|
||||
const re = reauth("b");
|
||||
h.update(
|
||||
values({
|
||||
activeServerId: "b",
|
||||
servers,
|
||||
inspectorClient: c,
|
||||
pendingStepUp: up,
|
||||
pendingReauth: re,
|
||||
}),
|
||||
);
|
||||
expect(h.ref().current).toMatchObject({
|
||||
activeServerId: "b",
|
||||
servers,
|
||||
inspectorClient: c,
|
||||
pendingStepUp: up,
|
||||
pendingReauth: re,
|
||||
});
|
||||
});
|
||||
|
||||
it("leaves the owner-written pending slots alone across renders", () => {
|
||||
const h = harness(values({ activeServerId: "a", servers: [entry("a")] }));
|
||||
// Stands in for `useOAuthRecovery`'s own mirror effect.
|
||||
h.ref().current.pendingStepUp = {
|
||||
serverId: "a",
|
||||
challenge: { reason: "unauthorized" },
|
||||
authorizationUrl: new URL("https://as.example/authorize"),
|
||||
source: "tool",
|
||||
};
|
||||
h.update(values({ activeServerId: "a", servers: [entry("a")] }));
|
||||
expect(h.ref().current.pendingStepUp?.serverId).toBe("a");
|
||||
});
|
||||
|
||||
it("clears a value that becomes null or undefined", () => {
|
||||
const h = harness(
|
||||
values({
|
||||
activeServerId: "a",
|
||||
servers: [entry("a")],
|
||||
inspectorClient: client(),
|
||||
pendingStepUp: stepUp("a"),
|
||||
pendingReauth: reauth("a"),
|
||||
}),
|
||||
);
|
||||
h.update(values());
|
||||
|
|
@ -134,8 +122,6 @@ describe("useSessionRef", () => {
|
|||
activeServerId: undefined,
|
||||
servers: [],
|
||||
inspectorClient: null,
|
||||
pendingStepUp: null,
|
||||
pendingReauth: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -24,12 +24,26 @@ export interface SessionSnapshot {
|
|||
* keeps the value the write replaced (#2089).
|
||||
*/
|
||||
inspectorClient: InspectorClient | null;
|
||||
/**
|
||||
* The two OAuth-recovery slots. Unlike the fields above they are **not**
|
||||
* supplied to this hook: they are owned by `useOAuthRecovery`, which mirrors
|
||||
* them in an effect of its own (#2153). Keeping them off `SessionValues`
|
||||
* is what lets that hook both own the state and read the ref — App.tsx
|
||||
* would otherwise have to hold the state purely to hand it back.
|
||||
*/
|
||||
pendingStepUp: PendingStepUp | null;
|
||||
pendingReauth: PendingReauth | null;
|
||||
}
|
||||
|
||||
/** What the caller supplies each render; `activeServerName` is derived. */
|
||||
export type SessionValues = Omit<SessionSnapshot, "activeServerName">;
|
||||
/**
|
||||
* What the caller supplies each render. `activeServerName` is derived, and the
|
||||
* two pending-OAuth slots are written by their owner — see
|
||||
* {@link SessionSnapshot}.
|
||||
*/
|
||||
export type SessionValues = Omit<
|
||||
SessionSnapshot,
|
||||
"activeServerName" | "pendingStepUp" | "pendingReauth"
|
||||
>;
|
||||
|
||||
/** A stable handle onto the latest {@link SessionSnapshot}. */
|
||||
export type SessionRef = RefObject<SessionSnapshot>;
|
||||
|
|
@ -60,6 +74,8 @@ export type SessionRef = RefObject<SessionSnapshot>;
|
|||
export function useSessionRef(values: SessionValues): SessionRef {
|
||||
const ref = useRef<SessionSnapshot>({
|
||||
activeServerName: undefined,
|
||||
pendingStepUp: null,
|
||||
pendingReauth: null,
|
||||
...values,
|
||||
});
|
||||
// Deliberately un-gated: this is a passive mirror, so re-assigning the same
|
||||
|
|
@ -73,8 +89,6 @@ export function useSessionRef(values: SessionValues): SessionRef {
|
|||
ref.current.activeServerId = values.activeServerId;
|
||||
ref.current.servers = values.servers;
|
||||
ref.current.inspectorClient = values.inspectorClient;
|
||||
ref.current.pendingStepUp = values.pendingStepUp;
|
||||
ref.current.pendingReauth = values.pendingReauth;
|
||||
});
|
||||
return ref;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue