import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import axios from "axios"; import { BROKER_EVENTS } from "../broker/broker-events"; vi.mock("axios"); const access_token = "test_access_token"; const refresh_token = "test_refresh_token"; const new_access_token = "new_access_token"; const new_refresh_token = "new_refresh_token"; const createMockBroker = () => ({ publish: vi.fn().mockResolvedValue(undefined), subscribe: vi.fn(), send: vi.fn(), registerRequest: vi.fn(), events: BROKER_EVENTS, }); describe("TokenManager", () => { let mockLocation: Partial; let TokenManagerImpl: any; let createTokenManager: any; let mockBroker: ReturnType; beforeEach(async () => { vi.resetModules(); mockLocation = { search: `?access_token=${access_token}&refresh_token=${refresh_token}`, }; vi.spyOn(window, "location", "get").mockReturnValue(mockLocation as Location); mockBroker = createMockBroker(); const module = await import("./token-manager"); TokenManagerImpl = module.TokenManagerImpl; createTokenManager = module.createTokenManager; }); afterEach(() => { vi.clearAllMocks(); }); describe("getToken", () => { it("should initialize token from URL params on first call", () => { const tokenManager = new TokenManagerImpl(mockBroker); expect(tokenManager.getToken()).toBe(access_token); }); it("should return empty string if URL params are missing", () => { mockLocation.search = ""; const tokenManager = new TokenManagerImpl(mockBroker); expect(tokenManager.getToken()).toBe(""); }); it("should return the same token on subsequent calls", () => { const tokenManager = new TokenManagerImpl(mockBroker); expect(tokenManager.getToken()).toBe(tokenManager.getToken()); }); }); describe("setInitialTokens", () => { it("should set tokens manually", () => { const tokenManager = new TokenManagerImpl(mockBroker); tokenManager.setInitialTokens("manual_access", "manual_refresh"); expect(tokenManager.getToken()).toBe("manual_access"); }); it("should throw error if tokens are already initialized", () => { const tokenManager = new TokenManagerImpl(mockBroker); tokenManager.setInitialTokens("manual_access", "manual_refresh"); expect(() => tokenManager.setInitialTokens("another_access", "another_refresh")).toThrow( "Token already initialized", ); }); it("should throw error if tokens were already initialized from URL", () => { const tokenManager = new TokenManagerImpl(mockBroker); tokenManager.getToken(); expect(() => tokenManager.setInitialTokens("manual_access", "manual_refresh")).toThrow( "Token already initialized", ); }); }); describe("refreshToken", () => { it("should refresh token successfully", async () => { vi.mocked(axios.post).mockResolvedValue({ data: { access_token: new_access_token, refresh_token: new_refresh_token }, }); const tokenManager = new TokenManagerImpl(mockBroker); tokenManager.setInitialTokens(access_token, refresh_token); const refreshed = await tokenManager.refreshToken(); expect(axios.post).toHaveBeenCalledWith("/api/token/refresh", { token: refresh_token }); expect(refreshed).toBe(new_access_token); expect(tokenManager.getToken()).toBe(new_access_token); }); it("should initialize tokens from URL before refreshing if not initialized", async () => { vi.mocked(axios.post).mockResolvedValue({ data: { access_token: new_access_token, refresh_token: new_refresh_token }, }); const tokenManager = new TokenManagerImpl(mockBroker); await tokenManager.refreshToken(); expect(axios.post).toHaveBeenCalledWith("/api/token/refresh", { token: refresh_token }); }); it("should throw error if refresh response doesn't contain access_token", async () => { vi.mocked(axios.post).mockResolvedValue({ data: {} }); const tokenManager = new TokenManagerImpl(mockBroker); tokenManager.setInitialTokens(access_token, refresh_token); await expect(tokenManager.refreshToken()).rejects.toThrow("Invalid refresh token response"); }); it("should publish refreshTokenFailed and rethrow when refresh fails", async () => { vi.mocked(axios.post).mockRejectedValue(new Error("Network error")); const tokenManager = new TokenManagerImpl(mockBroker); tokenManager.setInitialTokens(access_token, refresh_token); await expect(tokenManager.refreshToken()).rejects.toThrow("Network error"); expect(mockBroker.publish).toHaveBeenCalledWith(BROKER_EVENTS.shell.refreshTokenFailed, {}); }); it("should not publish refreshTokenFailed when refresh succeeds", async () => { vi.mocked(axios.post).mockResolvedValue({ data: { access_token: new_access_token, refresh_token: new_refresh_token }, }); const tokenManager = new TokenManagerImpl(mockBroker); tokenManager.setInitialTokens(access_token, refresh_token); await tokenManager.refreshToken(); expect(mockBroker.publish).not.toHaveBeenCalled(); }); }); describe("createTokenManager", () => { it("should return a singleton instance", () => { expect(createTokenManager(mockBroker)).toBe(createTokenManager(mockBroker)); }); }); });