///
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
AssistantCloudAnonymousAuthStrategy,
AssistantCloudJWTAuthStrategy,
readAnonymousRefreshToken,
} from "../AssistantCloudAuthStrategy";
import { CloudResponseError } from "../cloudResponse";
const baseUrl = "https://test.example.com";
const accessToken = `${Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url")}.${Buffer.from(JSON.stringify({ exp: 4102444800 })).toString("base64url")}.sig`;
const createAccessToken = (subject: string) =>
`${Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url")}.${Buffer.from(JSON.stringify({ exp: 4102444800, sub: subject })).toString("base64url")}.sig`;
const refreshToken = {
token: "r1",
expires_at: "2099-01-01",
};
const refreshTokenKey = `aui:refresh_token:${baseUrl}`;
let originalLocalStorageDescriptor: PropertyDescriptor | undefined;
const installLocalStorage = (storage: Storage): void => {
Object.defineProperty(globalThis, "localStorage", {
configurable: true,
value: storage,
});
};
const mockAnonymousTokenFetch = (nextRefreshToken = refreshToken) => {
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: nextRefreshToken,
}),
});
vi.stubGlobal("fetch", fetchMock);
return fetchMock;
};
describe("AssistantCloudAnonymousAuthStrategy", () => {
beforeEach(() => {
originalLocalStorageDescriptor = Object.getOwnPropertyDescriptor(
globalThis,
"localStorage",
);
vi.stubGlobal("navigator", {});
});
afterEach(() => {
vi.useRealTimers();
vi.unstubAllGlobals();
if (originalLocalStorageDescriptor) {
Object.defineProperty(
globalThis,
"localStorage",
originalLocalStorageDescriptor,
);
} else {
delete (globalThis as { localStorage?: Storage }).localStorage;
}
});
it("reads the stored anonymous refresh token", () => {
const values = new Map([[refreshTokenKey, JSON.stringify(refreshToken)]]);
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
expect(readAnonymousRefreshToken(baseUrl)).toBe(refreshToken.token);
});
it("reads the token stored for a base url given with a trailing slash", () => {
// The two-step cast below erases the contextual Storage typing the plain
// `as Storage` sites get, so this parameter needs its own annotation.
installLocalStorage({
getItem: (key: string) =>
key === refreshTokenKey ? JSON.stringify(refreshToken) : null,
setItem: vi.fn(),
removeItem: vi.fn(),
} as unknown as Storage);
expect(readAnonymousRefreshToken(`${baseUrl}/`)).toBe(refreshToken.token);
});
it("returns null when no anonymous refresh token is stored", () => {
installLocalStorage({
getItem: () => null,
setItem: vi.fn(),
removeItem: vi.fn(),
} as unknown as Storage);
expect(readAnonymousRefreshToken(baseUrl)).toBeNull();
});
it.each([30_000, 0, -1])(
"returns null when the stored anonymous refresh token expires in %i ms",
(expiresIn) => {
vi.useFakeTimers();
const now = Date.UTC(2026, 8, 4, 12, 0, 0);
vi.setSystemTime(now);
const values = new Map([
[
refreshTokenKey,
JSON.stringify({
token: refreshToken.token,
expires_at: new Date(now + expiresIn).toISOString(),
}),
],
]);
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
expect(readAnonymousRefreshToken(baseUrl)).toBeNull();
},
);
it.each([
"2099-01-01",
"2099-01-01T00:00:00Z",
"2099-01-01T00:00:00+0000",
"2099-01-01T00:00:00",
"2099-01-01 00:00:00+00",
])("persists refresh tokens with expiry %s", async (expiresAt) => {
const nextRefreshToken = { ...refreshToken, expires_at: expiresAt };
const values = new Map();
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
const fetchMock = mockAnonymousTokenFetch(nextRefreshToken);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(nextRefreshToken));
expect(fetchMock).toHaveBeenCalledWith(
`${baseUrl}/v1/auth/tokens/anonymous`,
{ method: "POST", signal: expect.any(AbortSignal) },
);
});
it("deduplicates concurrent anonymous token requests", async () => {
delete (globalThis as { localStorage?: Storage }).localStorage;
const fetchMock = mockAnonymousTokenFetch();
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(
Promise.all([
strategy.getAuthHeaders(),
strategy.getAuthHeaders(),
strategy.getAuthHeaders(),
]),
).resolves.toEqual([
{ Authorization: `Bearer ${accessToken}` },
{ Authorization: `Bearer ${accessToken}` },
{ Authorization: `Bearer ${accessToken}` },
]);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it("deduplicates anonymous token requests across strategy instances", async () => {
const values = new Map();
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
const fetchMock = mockAnonymousTokenFetch();
const first = new AssistantCloudAnonymousAuthStrategy(baseUrl);
const second = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(
Promise.all([first.getAuthHeaders(), second.getAuthHeaders()]),
).resolves.toEqual([
{ Authorization: `Bearer ${accessToken}` },
{ Authorization: `Bearer ${accessToken}` },
]);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
});
it("reuses an in-flight anonymous token request after invalidation", async () => {
const values = new Map();
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
let resolveResponse: (response: Response) => void = () => {};
const response = new Promise((resolve) => {
resolveResponse = resolve;
});
const fetchMock = vi
.fn()
.mockImplementationOnce(() => response);
vi.stubGlobal("fetch", fetchMock);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
const firstRequest = strategy.getAuthHeaders();
await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledOnce());
strategy.invalidate();
const secondRequest = strategy.getAuthHeaders();
expect(fetchMock).toHaveBeenCalledOnce();
resolveResponse({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: refreshToken,
}),
} as unknown as Response);
await expect(firstRequest).resolves.toBe(false);
await expect(secondRequest).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
await expect(strategy.getAuthHeaders()).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
expect(fetchMock).toHaveBeenCalledOnce();
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
});
it("coordinates anonymous token requests across realms", async () => {
const values = new Map();
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
let lockTail: Promise = Promise.resolve();
const lockRequest = vi.fn(
(_name: string, callback: () => Promise) => {
const request = lockTail.then(callback);
lockTail = request.then(
() => undefined,
() => undefined,
);
return request;
},
);
vi.stubGlobal("navigator", { locks: { request: lockRequest } });
const rotatedRefreshToken = { token: "r2", expires_at: "2099-01-01" };
const response = (refreshTokenValue: typeof refreshToken) => ({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: refreshTokenValue,
}),
});
const fetchMock = vi
.fn()
.mockResolvedValueOnce(response(refreshToken))
.mockResolvedValueOnce(response(rotatedRefreshToken));
vi.stubGlobal("fetch", fetchMock);
const first = new AssistantCloudAnonymousAuthStrategy(baseUrl);
vi.resetModules();
const { AssistantCloudAnonymousAuthStrategy: Second } =
await import("../AssistantCloudAuthStrategy");
await expect(
Promise.all([
first.getAuthHeaders(),
new Second(baseUrl).getAuthHeaders(),
]),
).resolves.toEqual([
{ Authorization: `Bearer ${accessToken}` },
{ Authorization: `Bearer ${accessToken}` },
]);
expect(fetchMock).toHaveBeenNthCalledWith(
1,
`${baseUrl}/v1/auth/tokens/anonymous`,
{ method: "POST", signal: expect.any(AbortSignal) },
);
expect(fetchMock).toHaveBeenNthCalledWith(
2,
`${baseUrl}/v1/auth/tokens/refresh`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: refreshToken.token }),
signal: expect.any(AbortSignal),
},
);
expect(lockRequest).toHaveBeenCalledTimes(2);
expect(values.get(refreshTokenKey)).toBe(
JSON.stringify(rotatedRefreshToken),
);
});
it("retries shared anonymous token requests after a failure", async () => {
const values = new Map();
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
const failure = new Error("authentication unavailable");
const fetchMock = vi
.fn()
.mockRejectedValueOnce(failure)
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: refreshToken,
}),
});
vi.stubGlobal("fetch", fetchMock);
const first = new AssistantCloudAnonymousAuthStrategy(baseUrl);
const second = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await Promise.all([
expect(first.getAuthHeaders()).rejects.toBe(failure),
expect(second.getAuthHeaders()).rejects.toBe(failure),
]);
expect(fetchMock).toHaveBeenCalledTimes(1);
await expect(
new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders(),
).resolves.toEqual({ Authorization: `Bearer ${accessToken}` });
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it("aborts shared anonymous token requests at the deadline before retrying", async () => {
vi.useFakeTimers();
const values = new Map();
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
let requestSignal: AbortSignal | null | undefined;
const fetchMock = vi
.fn()
.mockImplementationOnce((_input: RequestInfo | URL, init?: RequestInit) =>
Promise.resolve({
ok: true,
json: () =>
new Promise((_resolve, reject) => {
requestSignal = init?.signal;
requestSignal?.addEventListener(
"abort",
() => reject(requestSignal?.reason),
{ once: true },
);
}),
} as unknown as Response),
)
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: refreshToken,
}),
});
vi.stubGlobal("fetch", fetchMock);
const first = new AssistantCloudAnonymousAuthStrategy(baseUrl);
const second = new AssistantCloudAnonymousAuthStrategy(baseUrl);
const firstRequest = expect(first.getAuthHeaders()).rejects.toThrow(
"Assistant Cloud anonymous token request timed out after 30000ms",
);
const secondRequest = expect(second.getAuthHeaders()).rejects.toThrow(
"Assistant Cloud anonymous token request timed out after 30000ms",
);
await vi.advanceTimersByTimeAsync(0);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(requestSignal).toBeInstanceOf(AbortSignal);
await vi.advanceTimersByTimeAsync(29_999);
expect.soft(requestSignal?.aborted).toBe(false);
await vi.advanceTimersByTimeAsync(1);
await Promise.all([firstRequest, secondRequest]);
expect(requestSignal?.aborted).toBe(true);
expect(values.has(refreshTokenKey)).toBe(false);
await expect(
new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders(),
).resolves.toEqual({ Authorization: `Bearer ${accessToken}` });
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it("aborts refresh requests at the deadline without replacing the identity", async () => {
vi.useFakeTimers();
const values = new Map([[refreshTokenKey, JSON.stringify(refreshToken)]]);
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
let requestSignal: AbortSignal | null | undefined;
const rotatedRefreshToken = { token: "r2", expires_at: "2099-02-01" };
const fetchMock = vi
.fn()
.mockImplementationOnce(
(_input: RequestInfo | URL, init?: RequestInit) =>
new Promise((_resolve, reject) => {
requestSignal = init?.signal;
requestSignal?.addEventListener(
"abort",
() => reject(requestSignal?.reason),
{ once: true },
);
}),
)
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: rotatedRefreshToken,
}),
});
vi.stubGlobal("fetch", fetchMock);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
const request = expect(strategy.getAuthHeaders()).rejects.toThrow(
"Assistant Cloud refresh token request timed out after 30000ms",
);
expect(requestSignal).toBeInstanceOf(AbortSignal);
await vi.advanceTimersByTimeAsync(29_999);
expect.soft(requestSignal?.aborted).toBe(false);
await vi.advanceTimersByTimeAsync(1);
await request;
expect(requestSignal?.aborted).toBe(true);
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
await expect(strategy.getAuthHeaders()).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(values.get(refreshTokenKey)).toBe(
JSON.stringify(rotatedRefreshToken),
);
});
it("keeps anonymous token requests independent without localStorage", async () => {
delete (globalThis as { localStorage?: Storage }).localStorage;
const lockRequest = vi.fn();
vi.stubGlobal("navigator", { locks: { request: lockRequest } });
const fetchMock = mockAnonymousTokenFetch();
const first = new AssistantCloudAnonymousAuthStrategy(baseUrl);
const second = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(
Promise.all([first.getAuthHeaders(), second.getAuthHeaders()]),
).resolves.toEqual([
{ Authorization: `Bearer ${accessToken}` },
{ Authorization: `Bearer ${accessToken}` },
]);
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(lockRequest).not.toHaveBeenCalled();
});
it("scopes anonymous refresh tokens by backend", async () => {
const secondBaseUrl = "https://other.example.com";
const values = new Map();
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
const secondRefreshToken = {
token: "r2",
expires_at: "2099-01-01",
};
const fetchMock = vi
.fn()
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: refreshToken,
}),
})
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: secondRefreshToken,
}),
});
vi.stubGlobal("fetch", fetchMock);
await Promise.all([
new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders(),
new AssistantCloudAnonymousAuthStrategy(secondBaseUrl).getAuthHeaders(),
]);
expect(fetchMock).toHaveBeenNthCalledWith(
1,
`${baseUrl}/v1/auth/tokens/anonymous`,
{ method: "POST", signal: expect.any(AbortSignal) },
);
expect(fetchMock).toHaveBeenNthCalledWith(
2,
`${secondBaseUrl}/v1/auth/tokens/anonymous`,
{ method: "POST", signal: expect.any(AbortSignal) },
);
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
expect(values.get(`aui:refresh_token:${secondBaseUrl}`)).toBe(
JSON.stringify(secondRefreshToken),
);
});
it("migrates the legacy refresh token to the first backend", async () => {
const secondBaseUrl = "https://other.example.com";
const secondRefreshToken = {
token: "r2",
expires_at: "2099-01-01",
};
const values = new Map([
["aui:refresh_token", JSON.stringify(refreshToken)],
]);
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
const fetchMock = vi
.fn()
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({ access_token: accessToken }),
})
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: secondRefreshToken,
}),
});
vi.stubGlobal("fetch", fetchMock);
await new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders();
await new AssistantCloudAnonymousAuthStrategy(
secondBaseUrl,
).getAuthHeaders();
expect(fetchMock).toHaveBeenNthCalledWith(
1,
`${baseUrl}/v1/auth/tokens/refresh`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: refreshToken.token }),
signal: expect.any(AbortSignal),
},
);
expect(fetchMock).toHaveBeenNthCalledWith(
2,
`${secondBaseUrl}/v1/auth/tokens/anonymous`,
{ method: "POST", signal: expect.any(AbortSignal) },
);
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
expect(values.get(`aui:refresh_token:${secondBaseUrl}`)).toBe(
JSON.stringify(secondRefreshToken),
);
expect(values.has("aui:refresh_token")).toBe(false);
});
it("retires the legacy refresh token when a scoped token exists", async () => {
const secondBaseUrl = "https://other.example.com";
const scopedRefreshToken = {
token: "scoped-r1",
expires_at: "2099-01-01",
};
const secondRefreshToken = {
token: "r2",
expires_at: "2099-01-01",
};
const values = new Map([
["aui:refresh_token", JSON.stringify(refreshToken)],
[refreshTokenKey, JSON.stringify(scopedRefreshToken)],
]);
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
const fetchMock = vi
.fn()
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({ access_token: accessToken }),
})
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: secondRefreshToken,
}),
});
vi.stubGlobal("fetch", fetchMock);
await new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders();
await new AssistantCloudAnonymousAuthStrategy(
secondBaseUrl,
).getAuthHeaders();
expect(fetchMock).toHaveBeenNthCalledWith(
1,
`${baseUrl}/v1/auth/tokens/refresh`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: scopedRefreshToken.token }),
signal: expect.any(AbortSignal),
},
);
expect(fetchMock).toHaveBeenNthCalledWith(
2,
`${secondBaseUrl}/v1/auth/tokens/anonymous`,
{ method: "POST", signal: expect.any(AbortSignal) },
);
expect(values.has("aui:refresh_token")).toBe(false);
expect(values.get(`aui:refresh_token:${secondBaseUrl}`)).toBe(
JSON.stringify(secondRefreshToken),
);
});
it("returns an anonymous access token without localStorage", async () => {
delete (globalThis as { localStorage?: Storage }).localStorage;
mockAnonymousTokenFetch();
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
});
it("returns an anonymous access token when localStorage access is blocked", async () => {
Object.defineProperty(globalThis, "localStorage", {
configurable: true,
get() {
throw new DOMException("blocked", "SecurityError");
},
});
mockAnonymousTokenFetch();
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
});
it("returns an anonymous access token when storage methods throw", async () => {
const getItem = vi
.fn<(key: string) => string | null>()
.mockReturnValueOnce(
JSON.stringify({ token: "expired", expires_at: "2022-01-01" }),
)
.mockImplementation(() => {
throw new DOMException("blocked", "SecurityError");
});
const setItem = vi.fn(() => {
throw new DOMException("blocked", "SecurityError");
});
const removeItem = vi.fn(() => {
throw new DOMException("blocked", "SecurityError");
});
installLocalStorage({ getItem, setItem, removeItem } as unknown as Storage);
mockAnonymousTokenFetch();
await expect(
new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders(),
).resolves.toEqual({ Authorization: `Bearer ${accessToken}` });
await expect(
new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders(),
).resolves.toEqual({ Authorization: `Bearer ${accessToken}` });
expect(getItem).toHaveBeenCalledTimes(2);
expect(setItem).toHaveBeenCalledTimes(2);
expect(removeItem).toHaveBeenCalledTimes(2);
});
it("treats corrupted refresh token JSON as absent", async () => {
const values = new Map([["aui:refresh_token", "not-json{"]]);
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
mockAnonymousTokenFetch();
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
expect(values.has("aui:refresh_token")).toBe(false);
});
it("rejects malformed anonymous token responses without persisting them", async () => {
const setItem = vi.fn();
installLocalStorage({
getItem: () => null,
setItem,
removeItem: vi.fn(),
} as unknown as Storage);
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: "not-a-refresh-token",
}),
}),
);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).rejects.toThrow(
new CloudResponseError(
'Invalid Assistant Cloud response for "anonymous auth token response.refresh_token": expected an object',
),
);
expect(setItem).not.toHaveBeenCalled();
});
it("rejects empty refresh token expiry without persisting it", async () => {
const setItem = vi.fn();
installLocalStorage({
getItem: () => null,
setItem,
removeItem: vi.fn(),
} as unknown as Storage);
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: {
token: "r2",
expires_at: "",
},
}),
}),
);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).rejects.toThrow(
new CloudResponseError(
'Invalid Assistant Cloud response for "anonymous auth token response.refresh_token.expires_at": expected a non-empty string',
),
);
expect(setItem).not.toHaveBeenCalled();
});
it("contextualizes malformed refresh token responses", async () => {
installLocalStorage({
getItem: () => JSON.stringify(refreshToken),
setItem: vi.fn(),
removeItem: vi.fn(),
} as unknown as Storage);
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({ access_token: 123 }),
}),
);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).rejects.toThrow(
new CloudResponseError(
'Invalid Assistant Cloud response for "refresh auth token response.access_token": expected a string',
),
);
});
it("accepts valid refresh responses without a rotated refresh token", async () => {
const setItem = vi.fn();
installLocalStorage({
getItem: () => JSON.stringify(refreshToken),
setItem,
removeItem: vi.fn(),
} as unknown as Storage);
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: null,
}),
});
vi.stubGlobal("fetch", fetchMock);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
expect(fetchMock).toHaveBeenCalledWith(
`${baseUrl}/v1/auth/tokens/refresh`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: refreshToken.token }),
signal: expect.any(AbortSignal),
},
);
expect(setItem).not.toHaveBeenCalled();
});
it.each([429, 500, 503])(
"preserves the anonymous identity after transient status %i",
async (status) => {
const values = new Map([[refreshTokenKey, JSON.stringify(refreshToken)]]);
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
const fetchMock = vi.fn().mockResolvedValue({ ok: false, status });
vi.stubGlobal("fetch", fetchMock);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).rejects.toThrow(
`Assistant Cloud token refresh failed with status ${status}`,
);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
},
);
it.each([401, 403])(
"replaces an anonymous identity after refresh is rejected with %i",
async (status) => {
const replacementRefreshToken = {
token: "r2",
expires_at: "2099-02-01",
};
const values = new Map([[refreshTokenKey, JSON.stringify(refreshToken)]]);
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
const fetchMock = vi
.fn()
.mockResolvedValueOnce({ ok: false, status })
.mockResolvedValueOnce({
ok: true,
json: vi.fn().mockResolvedValue({
access_token: accessToken,
refresh_token: replacementRefreshToken,
}),
});
vi.stubGlobal("fetch", fetchMock);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
expect(fetchMock).toHaveBeenNthCalledWith(
2,
`${baseUrl}/v1/auth/tokens/anonymous`,
{ method: "POST", signal: expect.any(AbortSignal) },
);
expect(values.get(refreshTokenKey)).toBe(
JSON.stringify(replacementRefreshToken),
);
},
);
it("preserves a rejected refresh token until its replacement succeeds", async () => {
const values = new Map([[refreshTokenKey, JSON.stringify(refreshToken)]]);
installLocalStorage({
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => {
values.set(key, value);
},
removeItem: (key) => {
values.delete(key);
},
} as Storage);
vi.stubGlobal(
"fetch",
vi
.fn()
.mockResolvedValueOnce({ ok: false, status: 403 })
.mockResolvedValueOnce({ ok: false, status: 503 }),
);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).resolves.toBe(false);
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
});
it("contextualizes invalid JSON token responses", async () => {
delete (globalThis as { localStorage?: Storage }).localStorage;
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: true,
json: vi.fn().mockRejectedValue(new SyntaxError("Unexpected token")),
}),
);
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
await expect(strategy.getAuthHeaders()).rejects.toThrow(
new CloudResponseError(
'Invalid Assistant Cloud response for "anonymous auth token response": expected valid JSON',
),
);
});
});
describe("AssistantCloudJWTAuthStrategy", () => {
it("discards an in-flight provider token after invalidation", async () => {
let resolveUserAToken: (token: string) => void = () => {};
const userAToken = new Promise((resolve) => {
resolveUserAToken = resolve;
});
const authToken = vi
.fn<() => Promise>()
.mockImplementationOnce(() => userAToken)
.mockResolvedValueOnce(null);
const strategy = new AssistantCloudJWTAuthStrategy(authToken);
const userARequest = strategy.getAuthHeaders();
strategy.invalidate();
await expect(strategy.getAuthHeaders()).resolves.toBe(false);
resolveUserAToken(createAccessToken("user-a"));
await expect(userARequest).resolves.toBe(false);
expect(authToken).toHaveBeenCalledTimes(2);
});
it("shares an in-flight provider token request", async () => {
let resolveToken: (token: string) => void = () => {};
const token = new Promise((resolve) => {
resolveToken = resolve;
});
const authToken = vi.fn(() => token);
const strategy = new AssistantCloudJWTAuthStrategy(authToken);
const first = strategy.getAuthHeaders();
const second = strategy.getAuthHeaders();
resolveToken(accessToken);
await expect(first).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
await expect(second).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
expect(authToken).toHaveBeenCalledOnce();
});
it("retries token acquisition after a failed request", async () => {
const authToken = vi
.fn<() => Promise>()
.mockRejectedValueOnce(new Error("authentication unavailable"))
.mockResolvedValueOnce(accessToken);
const strategy = new AssistantCloudJWTAuthStrategy(authToken);
await expect(strategy.getAuthHeaders()).rejects.toThrow(
"authentication unavailable",
);
await expect(strategy.getAuthHeaders()).resolves.toEqual({
Authorization: `Bearer ${accessToken}`,
});
expect(authToken).toHaveBeenCalledTimes(2);
});
});