Merge remote-tracking branch 'origin/v2/main' into 1579-wave-1

# Conflicts:
#	clients/cli/package-lock.json
#	clients/cli/package.json
#	clients/web/src/App.tsx
#	clients/web/src/lib/environmentFactory.test.ts
#	clients/web/src/lib/environmentFactory.ts
#	clients/web/src/lib/remoteOAuthStorage.test.ts
#	clients/web/src/lib/remoteOAuthStorage.ts
#	clients/web/src/test/core/auth/connection-state.test.ts
#	clients/web/src/test/core/auth/providers.test.ts
#	clients/web/src/test/core/auth/storage-browser.test.ts
#	clients/web/src/test/core/auth/storage-remote.test.ts
#	clients/web/src/test/core/mcp/oauthManager.test.ts
#	clients/web/src/test/integration/auth/node/storage.test.ts
#	clients/web/src/test/integration/storage/adapters.test.ts
#	clients/web/src/utils/clearServerOAuthState.test.ts
#	clients/web/src/utils/clearServerOAuthState.ts
#	core/auth/connection-state.ts
#	core/auth/oauth-storage.ts
#	core/auth/providers.ts
#	core/auth/storage.ts
#	core/auth/store.ts
#	core/storage/adapters/remote-storage.ts
This commit is contained in:
cliffhall 2026-07-09 09:41:08 -04:00
commit 32a3610737
6 changed files with 5046 additions and 3271 deletions

View file

@ -83,7 +83,7 @@ describe("InspectorClient EMA E2E", () => {
});
beforeEach(async () => {
clearAllOAuthClientState();
await clearAllOAuthClientState();
vi.spyOn(console, "log").mockImplementation(() => {});
const keys = await createEmaMockKeyMaterial();
@ -199,14 +199,12 @@ describe("InspectorClient EMA E2E", () => {
await flushStoreFileWrites(oauthTestStatePath);
const raw = JSON.parse(await fs.readFile(oauthTestStatePath, "utf-8")) as {
state: {
servers: Record<
string,
{ tokens?: { access_token?: string }; enterpriseManaged?: boolean }
>;
};
servers: Record<
string,
{ tokens?: { access_token?: string }; enterpriseManaged?: boolean }
>;
};
const entry = raw.state.servers[mcpUrl];
const entry = raw.servers[mcpUrl];
expect(entry?.tokens?.access_token).toBeDefined();
expect(entry?.enterpriseManaged).toBe(true);
});

View file

@ -91,9 +91,9 @@ describe("InspectorClient OAuth E2E", () => {
}
});
beforeEach(() => {
beforeEach(async () => {
clearOAuthTestData();
clearAllOAuthClientState();
await clearAllOAuthClientState();
// Capture console.log output instead of printing to stdout during tests
vi.spyOn(console, "log").mockImplementation(() => {});
});
@ -630,7 +630,7 @@ describe("InspectorClient OAuth E2E", () => {
expect(client.getStatus()).toBe("connected");
await client.disconnect();
client.clearOAuthTokens();
await client.clearOAuthTokens();
const authUrlSecond = await client.authenticate();
if (!authUrlSecond) throw new Error("Expected authorization URL");
@ -995,7 +995,7 @@ describe("InspectorClient OAuth E2E", () => {
expect(tokens?.access_token).toBeDefined();
expect(await client.isOAuthAuthorized()).toBe(true);
client.clearOAuthTokens();
await client.clearOAuthTokens();
expect(await client.isOAuthAuthorized()).toBe(false);
expect(await client.getOAuthTokens()).toBeUndefined();
});
@ -1072,16 +1072,14 @@ describe("InspectorClient OAuth E2E", () => {
expect(client.getStatus()).toBe("connected");
type StateShape = {
state?: {
servers?: Record<string, { tokens?: { access_token?: string } }>;
};
servers?: Record<string, { tokens?: { access_token?: string } }>;
};
// Persistence is fire-and-forget; await the write rather than polling.
await flushStoreFileWrites(customPath);
const parsed = JSON.parse(
await fs.readFile(customPath, "utf-8"),
) as StateShape;
const servers = parsed.state?.servers ?? {};
const servers = parsed.servers ?? {};
expect(Object.keys(servers).length).toBeGreaterThan(0);
expect(
Object.values(servers).some((s) => !!s?.tokens?.access_token),

View file

@ -388,7 +388,7 @@ describe("InspectorClient remote mid-session OAuth", () => {
expect(recovery?.authChallenge.reason).toBe("insufficient_scope");
const mcpServerUrl = `${serverUrl}/mcp`;
const scopeBeforeStepUp = oauthConfig.storage.getScope(mcpServerUrl);
const scopeBeforeStepUp = await oauthConfig.storage.getScope(mcpServerUrl);
expect(scopeBeforeStepUp).toContain("mcp");
expect(scopeBeforeStepUp).not.toContain("weather:read");
@ -413,7 +413,7 @@ describe("InspectorClient remote mid-session OAuth", () => {
expect(callbackClient.getStatus()).toBe("connected");
const scopeAfterStepUp = oauthConfig.storage.getScope(mcpServerUrl);
const scopeAfterStepUp = await oauthConfig.storage.getScope(mcpServerUrl);
expect(scopeAfterStepUp).toContain("weather:read");
expect(scopeAfterStepUp).toContain("mcp");

View file

@ -352,18 +352,11 @@ describe("InspectorClient OAuth E2E with Remote Storage", () => {
remoteAuthToken!,
(body) => {
const b = body as {
state?: {
servers?: Record<
string,
{ tokens?: { access_token?: string } }
>;
};
servers?: Record<string, { tokens?: { access_token?: string } }>;
};
return !!(
b?.state?.servers &&
Object.values(b.state.servers).some(
(s) => s?.tokens?.access_token,
)
b?.servers &&
Object.values(b.servers).some((s) => s?.tokens?.access_token)
);
},
);

View file

@ -173,8 +173,8 @@ describe("InspectorClient OAuth", () => {
expect(tokens).toBeUndefined();
});
it("should clear OAuth tokens", () => {
client.clearOAuthTokens();
it("should clear OAuth tokens", async () => {
await client.clearOAuthTokens();
// Should not throw
expect(client).toBeDefined();
});

View file

@ -4959,7 +4959,7 @@ describe("InspectorClient", () => {
expect(c.getOAuthFlowState()).toBeUndefined();
await expect(c.getOAuthState()).resolves.toBeUndefined();
// clearOAuthTokens is a no-op when there is no manager
expect(() => c.clearOAuthTokens()).not.toThrow();
await expect(c.clearOAuthTokens()).resolves.toBeUndefined();
});
it("setOAuthConfig throws when oauthManager is unset", () => {