Removed Zustand storage, cleanup up auth storage IO interfaces, implementations, and usage.

This commit is contained in:
Bob Dickinson 2026-07-04 21:56:45 -07:00
commit 3c65bdb4c6
9 changed files with 863 additions and 1008 deletions

View file

@ -24,7 +24,7 @@ describe("ensureCimdClientRegistration", () => {
storage = {
getClientInformation: vi.fn(async () => undefined),
saveClientInformation: vi.fn(async () => {}),
getScope: vi.fn(() => undefined),
getScope: vi.fn().mockResolvedValue(undefined),
getTokens: vi.fn(async () => undefined),
saveTokens: vi.fn(async () => {}),
clear: vi.fn(),

View file

@ -14,11 +14,13 @@ function createStorage(
tokens: Awaited<ReturnType<OAuthStorage["getTokens"]>>;
preregistered: Awaited<ReturnType<OAuthStorage["getClientInformation"]>>;
dynamic: Awaited<ReturnType<OAuthStorage["getClientInformation"]>>;
registrationKind: ReturnType<OAuthStorage["getClientRegistrationKind"]>;
registrationKind: Awaited<
ReturnType<OAuthStorage["getClientRegistrationKind"]>
>;
scope: string | undefined;
serverMetadata: ReturnType<OAuthStorage["getServerMetadata"]>;
serverMetadata: Awaited<ReturnType<OAuthStorage["getServerMetadata"]>>;
idpSession: Awaited<ReturnType<OAuthStorage["getIdpSession"]>>;
idpMetadata: ReturnType<OAuthStorage["getServerMetadata"]>;
idpMetadata: Awaited<ReturnType<OAuthStorage["getServerMetadata"]>>;
}> = {},
): OAuthStorage {
return {
@ -27,15 +29,17 @@ function createStorage(
getClientInformation: vi.fn(async (_url, isPreregistered) =>
isPreregistered ? overrides.preregistered : overrides.dynamic,
),
getScope: vi.fn().mockReturnValue(overrides.scope),
getServerMetadata: vi.fn((url: string) => {
getScope: vi.fn().mockResolvedValue(overrides.scope),
getServerMetadata: vi.fn(async (url: string) => {
if (url.startsWith("ema-idp:")) {
return overrides.idpMetadata ?? null;
}
return overrides.serverMetadata ?? null;
}),
getIdpSession: vi.fn().mockResolvedValue(overrides.idpSession),
getClientRegistrationKind: vi.fn(() => overrides.registrationKind),
getClientRegistrationKind: vi
.fn()
.mockResolvedValue(overrides.registrationKind),
saveClientInformation: vi.fn(),
savePreregisteredClientInformation: vi.fn(),
saveTokens: vi.fn(),

View file

@ -49,19 +49,21 @@ function createMemoryStorage(
idpSessions[issuer] = { ...idpSessions[issuer], ...updates };
}),
clearIdpSession: vi.fn(),
getServerMetadata: vi.fn((key: string) => metadataByKey[key]),
saveServerMetadata: vi.fn((key: string, metadata: unknown) => {
getServerMetadata: vi.fn(async (key: string) => metadataByKey[key] ?? null),
saveServerMetadata: vi.fn(async (key: string, metadata: unknown) => {
metadataByKey[key] = metadata;
}),
getClientInformation: vi.fn((key: string) => clientInfoByKey[key]),
savePreregisteredClientInformation: vi.fn((key: string, info: unknown) => {
clientInfoByKey[key] = info;
}),
getCodeVerifier: vi.fn((key: string) => codeVerifierByKey[key]),
saveCodeVerifier: vi.fn((key: string, verifier: string) => {
getClientInformation: vi.fn(async (key: string) => clientInfoByKey[key]),
savePreregisteredClientInformation: vi.fn(
async (key: string, info: unknown) => {
clientInfoByKey[key] = info;
},
),
getCodeVerifier: vi.fn(async (key: string) => codeVerifierByKey[key]),
saveCodeVerifier: vi.fn(async (key: string, verifier: string) => {
codeVerifierByKey[key] = verifier;
}),
clearCodeVerifier: vi.fn((key: string) => {
clearCodeVerifier: vi.fn(async (key: string) => {
delete codeVerifierByKey[key];
}),
getTokens: vi.fn(async (url: string) => savedTokens[url] as never),

View file

@ -34,7 +34,7 @@ describe("idpOidc refresh", () => {
saveIdpSession: vi.fn(async (issuer: string, updates) => {
idpSessions[issuer] = { ...idpSessions[issuer], ...updates };
}),
getServerMetadata: vi.fn(() => minimalOAuthAsMetadata(IDP_ISSUER)),
getServerMetadata: vi.fn(async () => minimalOAuthAsMetadata(IDP_ISSUER)),
clearIdpSession: vi.fn(),
} as unknown as OAuthStorage;
});
@ -266,7 +266,7 @@ describe("startIdpOidcAuthorization", () => {
Object.keys(saved).forEach((k) => delete saved[k]);
storage = {
load: vi.fn().mockResolvedValue(undefined),
getServerMetadata: vi.fn(() => null),
getServerMetadata: vi.fn(async () => null),
saveCodeVerifier: vi.fn(async (key: string, value: string) => {
saved[`cv:${key}`] = value;
}),
@ -335,13 +335,13 @@ describe("completeIdpOidcAuthorization", () => {
function buildStorage(overrides: Partial<OAuthStorage> = {}): OAuthStorage {
return {
load: vi.fn().mockResolvedValue(undefined),
getServerMetadata: vi.fn(() => minimalOAuthAsMetadata(IDP_ISSUER)),
getServerMetadata: vi.fn(async () => minimalOAuthAsMetadata(IDP_ISSUER)),
getClientInformation: vi.fn(async () => ({
client_id: "idp-client",
client_secret: "idp-secret",
token_endpoint_auth_method: "client_secret_post",
})),
getCodeVerifier: vi.fn(() => "verifier-123"),
getCodeVerifier: vi.fn(async () => "verifier-123"),
clearCodeVerifier: vi.fn((key: string) => {
clearedVerifiers.push(key);
}),
@ -474,7 +474,7 @@ describe("completeIdpOidcAuthorization", () => {
});
it("throws when the PKCE verifier is missing", async () => {
storage = buildStorage({ getCodeVerifier: vi.fn(() => undefined) });
storage = buildStorage({ getCodeVerifier: vi.fn(async () => undefined) });
await expect(
completeIdpOidcAuthorization({
idp,

View file

@ -0,0 +1,54 @@
import { describe, it, expect } from "vitest";
import {
parseOAuthPersistBlob,
serializeOAuthPersistBlob,
} from "@inspector/core/auth/oauth-persist.js";
describe("parseOAuthPersistBlob", () => {
it("returns null for empty input", () => {
expect(parseOAuthPersistBlob(null)).toBeNull();
});
it("reads plain JSON with servers and idpSessions", () => {
const snapshot = {
servers: {
"http://example.com": { codeVerifier: "v1" },
},
idpSessions: {
"https://idp.example": { idToken: "token" },
},
};
expect(parseOAuthPersistBlob(JSON.stringify(snapshot))).toEqual(snapshot);
});
it("promotes legacy persist envelope state to the top level", () => {
const legacy = {
state: {
servers: {
"http://example.com": {
tokens: { access_token: "t", token_type: "Bearer" },
},
},
idpSessions: {},
},
version: 0,
};
expect(parseOAuthPersistBlob(JSON.stringify(legacy))).toEqual({
servers: legacy.state.servers,
idpSessions: {},
});
});
});
describe("serializeOAuthPersistBlob", () => {
it("writes plain JSON without a state/version envelope", () => {
const snapshot = {
servers: { "http://example.com": { scope: "read" } },
idpSessions: {},
};
const raw = serializeOAuthPersistBlob(snapshot);
expect(JSON.parse(raw)).toEqual(snapshot);
expect(raw).not.toContain('"version"');
expect(raw).not.toMatch(/"state"\s*:/);
});
});

View file

@ -214,7 +214,7 @@ describe("OAuthNavigation", () => {
function makeStorage(): OAuthStorage {
return {
load: vi.fn().mockResolvedValue(undefined),
getScope: vi.fn(() => undefined),
getScope: vi.fn().mockResolvedValue(undefined),
getClientInformation: vi.fn(async () => undefined),
saveClientInformation: vi.fn(async () => undefined),
savePreregisteredClientInformation: vi.fn(async () => undefined),
@ -222,9 +222,9 @@ describe("OAuthNavigation", () => {
getTokens: vi.fn(async () => undefined),
saveTokens: vi.fn(async () => undefined),
saveCodeVerifier: vi.fn(async () => undefined),
getCodeVerifier: vi.fn(() => undefined),
getCodeVerifier: vi.fn().mockResolvedValue(undefined),
clear: vi.fn(),
getServerMetadata: vi.fn(() => null),
getServerMetadata: vi.fn().mockResolvedValue(null),
saveServerMetadata: vi.fn(async () => undefined),
} as unknown as OAuthStorage;
}
@ -272,27 +272,28 @@ describe("OAuthNavigation", () => {
expect(await provider.clientInformation()).toEqual({ client_id: "dyn" });
});
it("codeVerifier() throws when none is saved", () => {
it("codeVerifier() throws when none is saved", async () => {
const storage = makeStorage();
const provider = makeProvider(storage);
expect(() => provider.codeVerifier()).toThrow(
await expect(provider.codeVerifier()).rejects.toThrow(
/No code verifier saved for session/,
);
});
it("codeVerifier() returns the saved verifier", () => {
it("codeVerifier() returns the saved verifier", async () => {
const storage = makeStorage();
vi.mocked(storage.getCodeVerifier).mockReturnValue("cv-1");
vi.mocked(storage.getCodeVerifier).mockResolvedValue("cv-1");
const provider = makeProvider(storage);
expect(provider.codeVerifier()).toBe("cv-1");
expect(await provider.codeVerifier()).toBe("cv-1");
});
it("clientMetadata reflects the stored scope when present", () => {
it("clientMetadata reflects the stored scope when present", async () => {
const storage = makeStorage();
vi.mocked(storage.getScope).mockReturnValue("read write");
vi.mocked(storage.getScope).mockResolvedValue("read write");
const provider = makeProvider(storage);
await provider.prepareForAuth();
expect(provider.clientMetadata.scope).toBe("read write");
});
@ -380,7 +381,7 @@ describe("OAuthNavigation", () => {
expect(storage.saveCodeVerifier).toHaveBeenCalledWith(SERVER, "cv");
expect(storage.saveServerMetadata).toHaveBeenCalled();
expect(await provider.tokens()).toBeUndefined();
expect(provider.getServerMetadata()).toBeNull();
expect(await provider.getServerMetadata()).toBeNull();
const state = await provider.state();
expect(typeof state).toBe("string");
expect(state.length).toBeGreaterThan(0);

View file

@ -91,16 +91,11 @@ describe("BrowserOAuthStorage", () => {
client_secret: "preregistered-secret",
};
// Use the storage API instead of manually setting sessionStorage
// since BrowserOAuthStorage now uses Zustand with a different storage format
await storage.savePreregisteredClientInformation(
testServerUrl,
preregisteredInfo,
);
// Wait for Zustand to persist
await new Promise((resolve) => setTimeout(resolve, 100));
const result = await storage.getClientInformation(testServerUrl, true);
expect(result).toEqual(preregisteredInfo);
@ -139,14 +134,18 @@ describe("BrowserOAuthStorage", () => {
const result = await storage.getClientInformation(testServerUrl);
expect(result).toEqual(secondInfo);
expect(storage.getClientRegistrationKind(testServerUrl)).toBe("cimd");
expect(await storage.getClientRegistrationKind(testServerUrl)).toBe(
"cimd",
);
});
it("savePreregisteredClientInformation sets static registration kind", async () => {
await storage.savePreregisteredClientInformation(testServerUrl, {
client_id: "static-id",
});
expect(storage.getClientRegistrationKind(testServerUrl)).toBe("static");
expect(await storage.getClientRegistrationKind(testServerUrl)).toBe(
"static",
);
});
});
@ -333,16 +332,16 @@ describe("BrowserOAuthStorage", () => {
it("clearCodeVerifier removes only the PKCE verifier", async () => {
await storage.saveCodeVerifier(testServerUrl, "verifier");
expect(storage.getCodeVerifier(testServerUrl)).toBe("verifier");
expect(await storage.getCodeVerifier(testServerUrl)).toBe("verifier");
await storage.clearCodeVerifier(testServerUrl);
expect(storage.getCodeVerifier(testServerUrl)).toBeUndefined();
expect(await storage.getCodeVerifier(testServerUrl)).toBeUndefined();
});
it("clearScope removes only the scope", async () => {
await storage.saveScope(testServerUrl, "read");
expect(storage.getScope(testServerUrl)).toBe("read");
expect(await storage.getScope(testServerUrl)).toBe("read");
await storage.clearScope(testServerUrl);
expect(storage.getScope(testServerUrl)).toBeUndefined();
expect(await storage.getScope(testServerUrl)).toBeUndefined();
});
it("clearServerMetadata removes only the cached metadata", async () => {
@ -353,9 +352,9 @@ describe("BrowserOAuthStorage", () => {
response_types_supported: ["code"],
};
await storage.saveServerMetadata(testServerUrl, metadata);
expect(storage.getServerMetadata(testServerUrl)).toEqual(metadata);
expect(await storage.getServerMetadata(testServerUrl)).toEqual(metadata);
await storage.clearServerMetadata(testServerUrl);
expect(storage.getServerMetadata(testServerUrl)).toBeNull();
expect(await storage.getServerMetadata(testServerUrl)).toBeNull();
});
});

View file

@ -1,9 +1,5 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { RemoteOAuthStorage } from "@inspector/core/auth/remote/storage-remote.js";
import {
type OAuthStore,
waitForOAuthStorePersistLoad,
} from "@inspector/core/auth/store.js";
const NOOP_FETCH = vi.fn(
async () =>
@ -76,16 +72,16 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
it("codeVerifier round-trip and clearCodeVerifier", async () => {
await storage.saveCodeVerifier(serverUrl, "verifier");
expect(storage.getCodeVerifier(serverUrl)).toBe("verifier");
expect(await storage.getCodeVerifier(serverUrl)).toBe("verifier");
await storage.clearCodeVerifier(serverUrl);
expect(storage.getCodeVerifier(serverUrl)).toBeUndefined();
expect(await storage.getCodeVerifier(serverUrl)).toBeUndefined();
});
it("scope round-trip and clearScope", async () => {
await storage.saveScope(serverUrl, "read write");
expect(storage.getScope(serverUrl)).toBe("read write");
expect(await storage.getScope(serverUrl)).toBe("read write");
await storage.clearScope(serverUrl);
expect(storage.getScope(serverUrl)).toBeUndefined();
expect(await storage.getScope(serverUrl)).toBeUndefined();
});
it("serverMetadata round-trip and clearServerMetadata", async () => {
@ -96,9 +92,9 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
response_types_supported: ["code"],
};
await storage.saveServerMetadata(serverUrl, md);
expect(storage.getServerMetadata(serverUrl)).toEqual(md);
expect(await storage.getServerMetadata(serverUrl)).toEqual(md);
await storage.clearServerMetadata(serverUrl);
expect(storage.getServerMetadata(serverUrl)).toBeNull();
expect(await storage.getServerMetadata(serverUrl)).toBeNull();
});
it("clear() wipes all state for a server", async () => {
@ -125,13 +121,7 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
expect(s).toBeInstanceOf(RemoteOAuthStorage);
});
it("waitForOAuthStorePersistLoad resolves for an unregistered store handle", async () => {
await expect(
waitForOAuthStorePersistLoad({} as OAuthStore),
).resolves.toBeUndefined();
});
it("load() waits for remote GET before sync reads see persisted state", async () => {
it("getCodeVerifier loads remote state automatically when not preloaded", async () => {
const persisted = {
state: {
servers: {
@ -155,15 +145,10 @@ describe("RemoteOAuthStorage (unit, mocked fetch)", () => {
fetchFn: delayedFetch,
});
const loadPromise = delayedStorage.load();
expect(delayedStorage.getCodeVerifier(serverUrl)).toBeUndefined();
const getPromise = delayedStorage.getCodeVerifier(serverUrl);
resolveFetch(new Response(JSON.stringify(persisted), { status: 200 }));
await loadPromise;
expect(delayedStorage.getCodeVerifier(serverUrl)).toBe(
"persisted-verifier",
);
expect(await getPromise).toBe("persisted-verifier");
});
it("load() rejects when remote GET fails instead of hanging", async () => {

View file

@ -36,15 +36,15 @@ function createMockParams(
const storage = {
load: vi.fn().mockResolvedValue(undefined),
getScope: vi.fn().mockReturnValue(undefined),
getScope: vi.fn().mockResolvedValue(undefined),
getClientInformation: vi.fn().mockResolvedValue(undefined),
getClientRegistrationKind: vi.fn().mockReturnValue(undefined),
getClientRegistrationKind: vi.fn().mockResolvedValue(undefined),
saveClientInformation: vi.fn().mockResolvedValue(undefined),
savePreregisteredClientInformation: vi.fn().mockResolvedValue(undefined),
saveScope: vi.fn().mockResolvedValue(undefined),
getTokens: vi.fn().mockResolvedValue(undefined),
saveTokens: vi.fn().mockResolvedValue(undefined),
getCodeVerifier: vi.fn().mockReturnValue("verifier"),
getCodeVerifier: vi.fn().mockResolvedValue("verifier"),
saveCodeVerifier: vi.fn().mockResolvedValue(undefined),
clear: vi.fn().mockResolvedValue(undefined),
clearClientInformation: vi.fn(),
@ -52,7 +52,7 @@ function createMockParams(
clearCodeVerifier: vi.fn(),
clearScope: vi.fn(),
clearServerMetadata: vi.fn(),
getServerMetadata: vi.fn().mockReturnValue(null),
getServerMetadata: vi.fn().mockResolvedValue(null),
saveServerMetadata: vi.fn().mockResolvedValue(undefined),
getIdpSession: vi.fn().mockResolvedValue(undefined),
saveIdpSession: vi.fn().mockResolvedValue(undefined),
@ -428,7 +428,7 @@ describe("OAuthManager", () => {
const params = createMockParams({ onBeforeOAuthRedirect });
// A configured scope exercises the saveScope branch in createOAuthProvider.
params.initialConfig.scope = "read write";
storageOf(params).getScope.mockReturnValue(undefined);
storageOf(params).getScope.mockResolvedValue(undefined);
storageOf(params).getClientInformation.mockResolvedValue({
client_id: "cid",
});
@ -466,7 +466,9 @@ describe("OAuthManager", () => {
mockedMcpAuth.mockResolvedValue("REDIRECT");
const params = createMockParams();
params.initialConfig.scope = "mcp tools:read";
storageOf(params).getScope.mockReturnValue("mcp tools:read weather:read");
storageOf(params).getScope.mockResolvedValue(
"mcp tools:read weather:read",
);
storageOf(params).getClientInformation.mockResolvedValue({
client_id: "cid",
});
@ -586,7 +588,7 @@ describe("OAuthManager", () => {
.mockResolvedValueOnce("REDIRECT")
.mockResolvedValueOnce("AUTHORIZED");
const params = createMockParams();
storageOf(params).getScope.mockReturnValue("mcp");
storageOf(params).getScope.mockResolvedValue("mcp");
storageOf(params).getTokens.mockResolvedValue({
access_token: "access",
refresh_token: "refresh",
@ -629,7 +631,7 @@ describe("OAuthManager", () => {
it("persists granted scope when AS down-scopes the token response", async () => {
mockedMcpAuth.mockResolvedValue("AUTHORIZED");
const params = createMockParams();
storageOf(params).getScope.mockReturnValue("mcp");
storageOf(params).getScope.mockResolvedValue("mcp");
storageOf(params).getTokens.mockResolvedValue({
access_token: "access",
token_type: "Bearer",
@ -984,7 +986,9 @@ describe("OAuthManager", () => {
token_type: "Bearer",
scope: "mcp tools:read tools:write",
});
storageOf(params).getScope.mockReturnValue("mcp tools:read tools:write");
storageOf(params).getScope.mockResolvedValue(
"mcp tools:read tools:write",
);
const manager = new OAuthManager(params);
expect(
@ -1002,7 +1006,7 @@ describe("OAuthManager", () => {
token_type: "Bearer",
scope: "mcp tools:read",
});
storageOf(params).getScope.mockReturnValue("mcp tools:read");
storageOf(params).getScope.mockResolvedValue("mcp tools:read");
const manager = new OAuthManager(params);
expect(
@ -1020,7 +1024,7 @@ describe("OAuthManager", () => {
token_type: "Bearer",
scope: "mcp",
});
storageOf(params).getScope.mockReturnValue("mcp weather:read");
storageOf(params).getScope.mockResolvedValue("mcp weather:read");
const manager = new OAuthManager(params);
expect(
@ -1037,7 +1041,7 @@ describe("OAuthManager", () => {
access_token: "tok",
token_type: "Bearer",
});
storageOf(params).getScope.mockReturnValue(undefined);
storageOf(params).getScope.mockResolvedValue(undefined);
const manager = new OAuthManager(params);
expect(
@ -1054,7 +1058,9 @@ describe("OAuthManager", () => {
token_type: "Bearer",
scope: "mcp tools:read tools:write",
});
storageOf(params).getScope.mockReturnValue("mcp tools:read tools:write");
storageOf(params).getScope.mockResolvedValue(
"mcp tools:read tools:write",
);
const manager = new OAuthManager(params);
const outcome = await manager.handleAuthChallenge({
@ -1136,7 +1142,7 @@ describe("OAuthManager", () => {
const params = createMockParams();
const manager = new OAuthManager(params);
manager.setOAuthConfig({ scope: "catalog:scope" });
storageOf(params).getScope.mockReturnValue("stored union scope");
storageOf(params).getScope.mockResolvedValue("stored union scope");
const captureSpy = vi
.spyOn(
(await import("@inspector/core/auth/providers.js"))
@ -1164,7 +1170,7 @@ describe("OAuthManager", () => {
token_type: "Bearer",
scope: "mcp tools:read",
});
storageOf(params).getScope.mockReturnValue("mcp tools:read");
storageOf(params).getScope.mockResolvedValue("mcp tools:read");
const manager = new OAuthManager(params);
const outcome = await manager.handleAuthChallenge({
@ -1201,7 +1207,7 @@ describe("OAuthManager", () => {
);
mockedMcpAuth.mockResolvedValue("REDIRECT");
const params = createMockParams();
storageOf(params).getScope.mockReturnValue("mcp tools:read");
storageOf(params).getScope.mockResolvedValue("mcp tools:read");
const manager = new OAuthManager(params);
const captureSpy = vi
.spyOn(
@ -1234,7 +1240,7 @@ describe("OAuthManager", () => {
);
mockedMcpAuth.mockResolvedValue("REDIRECT");
const params = createMockParams();
storageOf(params).getScope.mockReturnValue("mcp tools:read");
storageOf(params).getScope.mockResolvedValue("mcp tools:read");
storageOf(params).getTokens.mockResolvedValue({
access_token: "access",
refresh_token: "refresh",
@ -1322,7 +1328,7 @@ describe("OAuthManager", () => {
},
},
});
storageOf(params).getScope.mockReturnValue("mcp");
storageOf(params).getScope.mockResolvedValue("mcp");
storageOf(params).getTokens.mockResolvedValue({
access_token: "tok",
token_type: "Bearer",
@ -1360,7 +1366,7 @@ describe("OAuthManager", () => {
},
},
});
storageOf(params).getScope.mockReturnValue("mcp tools:read");
storageOf(params).getScope.mockResolvedValue("mcp tools:read");
storageOf(params).getTokens.mockResolvedValue({
access_token: "tok",
token_type: "Bearer",
@ -1453,7 +1459,7 @@ describe("OAuthManager", () => {
},
},
});
storageOf(params).getScope.mockReturnValue("mcp");
storageOf(params).getScope.mockResolvedValue("mcp");
storageOf(params).getTokens.mockResolvedValue({
access_token: "tok",
token_type: "Bearer",
@ -1501,7 +1507,7 @@ describe("OAuthManager", () => {
},
},
});
storageOf(params).getScope.mockReturnValue("mcp tools:read");
storageOf(params).getScope.mockResolvedValue("mcp tools:read");
storageOf(params).getTokens.mockResolvedValue({
access_token: "old",
token_type: "Bearer",
@ -1635,7 +1641,7 @@ describe("OAuthManager", () => {
token_type: "Bearer",
scope: "mcp tools:read weather:read",
};
storageOf(params).getScope.mockReturnValue("mcp tools:read");
storageOf(params).getScope.mockResolvedValue("mcp tools:read");
storageOf(params)
.getTokens.mockResolvedValueOnce(insufficientTokens)
.mockResolvedValue(sufficientTokens);
@ -1665,7 +1671,7 @@ describe("OAuthManager", () => {
token_type: "Bearer",
scope: "mcp tools:read weather:read",
};
storageOf(params).getScope.mockReturnValue("mcp tools:read");
storageOf(params).getScope.mockResolvedValue("mcp tools:read");
storageOf(params)
.getTokens.mockResolvedValueOnce(insufficientTokens)
.mockResolvedValueOnce(insufficientTokens)
@ -1801,7 +1807,7 @@ describe("OAuthManager", () => {
.mockResolvedValueOnce("AUTHORIZED")
.mockResolvedValueOnce("REDIRECT");
const params = createMockParams();
storageOf(params).getScope.mockReturnValue("mcp tools:read");
storageOf(params).getScope.mockResolvedValue("mcp tools:read");
storageOf(params).getTokens.mockResolvedValue({
access_token: "access",
token_type: "Bearer",