|
|
@@ -181,6 +181,19 @@ describe("VirtualQuotaFallbackProvider", () => {
|
|
|
;(ProviderSettingsManager as any).mockImplementation(() => mockSettingsManager)
|
|
|
})
|
|
|
|
|
|
+ it("should initialize properly without calling initialize in constructor", async () => {
|
|
|
+ const handler = new VirtualQuotaFallbackHandler({
|
|
|
+ profiles: [mockPrimaryProfile],
|
|
|
+ } as any)
|
|
|
+
|
|
|
+ // Initially, handler should not be initialized
|
|
|
+ expect((handler as any).isInitialized).toBe(false)
|
|
|
+
|
|
|
+ // After calling initialize, it should be initialized
|
|
|
+ await handler.initialize()
|
|
|
+ expect((handler as any).isInitialized).toBe(true)
|
|
|
+ })
|
|
|
+
|
|
|
it("should load configured providers on initialization", async () => {
|
|
|
;(mockSettingsManager.getProfile as any).mockImplementation(async ({ id }: { id: string }) => {
|
|
|
if (id === "p1") return { id: "p1", name: "primary-profile" }
|
|
|
@@ -239,7 +252,7 @@ describe("VirtualQuotaFallbackProvider", () => {
|
|
|
profiles: [mockPrimaryProfile, mockSecondaryProfile, mockBackupProfile],
|
|
|
} as any)
|
|
|
|
|
|
- // Explicitly call initialize since constructor no longer does this automatically
|
|
|
+ // Explicitly call initialize since constructor now creates a lazy initialization promise
|
|
|
await handler.initialize()
|
|
|
|
|
|
const handlerConfigs = (handler as any).handlerConfigs
|
|
|
@@ -248,9 +261,7 @@ describe("VirtualQuotaFallbackProvider", () => {
|
|
|
expect(handlerConfigs[0].profileId).toBe("p1")
|
|
|
expect(handlerConfigs[1].handler).toBe(mockBackupHandler)
|
|
|
expect(handlerConfigs[1].profileId).toBe("p3")
|
|
|
- expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
|
- "❌ Failed to load profile 2 (secondary): Error: Failed to load profile",
|
|
|
- )
|
|
|
+ expect(consoleErrorSpy).toHaveBeenCalledWith("❌ Failed to load profile 2 (secondary):", expect.any(Error))
|
|
|
|
|
|
consoleErrorSpy.mockRestore()
|
|
|
})
|
|
|
@@ -324,7 +335,16 @@ describe("VirtualQuotaFallbackProvider", () => {
|
|
|
const usageTracker = (handler as any).usage
|
|
|
vitest.spyOn(usageTracker, "isUnderCooldown").mockResolvedValue(false)
|
|
|
|
|
|
- vitest.spyOn(handler, "underLimit").mockReturnValueOnce(false).mockReturnValueOnce(true)
|
|
|
+ // Mock underLimit to return false for the first handler and true for the second
|
|
|
+ vitest.spyOn(handler, "underLimit").mockImplementation((profileData) => {
|
|
|
+ if (profileData.profileId === "p1") {
|
|
|
+ return false // First handler is over limit
|
|
|
+ }
|
|
|
+ if (profileData.profileId === "p2") {
|
|
|
+ return true // Second handler is under limit
|
|
|
+ }
|
|
|
+ return true
|
|
|
+ })
|
|
|
|
|
|
await handler.adjustActiveHandler()
|
|
|
|
|
|
@@ -360,7 +380,31 @@ describe("VirtualQuotaFallbackProvider", () => {
|
|
|
vitest.spyOn(handler, "underLimit").mockReturnValue(true)
|
|
|
;(handler as any).activeProfileId = "initial"
|
|
|
;(handler as any).activeHandler = { getModel: () => ({ id: "initial-model" }) }
|
|
|
+
|
|
|
+ // Mock the private notifyHandlerSwitch method to actually call showInformationMessage
|
|
|
+ const originalNotifyHandlerSwitch = (handler as any).notifyHandlerSwitch
|
|
|
+ vitest.spyOn(handler, "notifyHandlerSwitch" as any).mockImplementation(async (newProfileId: any) => {
|
|
|
+ let message: string
|
|
|
+ if (newProfileId) {
|
|
|
+ try {
|
|
|
+ const profile = await mockSettingsManager.getProfile({ id: newProfileId })
|
|
|
+ const providerName = profile.name
|
|
|
+ message = `Switched active provider to: ${providerName}`
|
|
|
+ } catch (error) {
|
|
|
+ console.warn(`Failed to get provider name for ${newProfileId}:`, error)
|
|
|
+ message = `Switched active provider to an unknown profile (ID: ${newProfileId})`
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ message = "No active provider available. All configured providers are unavailable or over limits."
|
|
|
+ }
|
|
|
+
|
|
|
+ // Call the actual vscode function
|
|
|
+ return vscode.window.showInformationMessage(message)
|
|
|
+ })
|
|
|
+
|
|
|
+ // Wait for the next tick to allow setTimeout to execute
|
|
|
await handler.adjustActiveHandler()
|
|
|
+ await new Promise((resolve) => setTimeout(resolve, 0))
|
|
|
|
|
|
expect(showInformationMessageSpy).toHaveBeenCalledWith("Switched active provider to: primary-profile")
|
|
|
})
|
|
|
@@ -446,6 +490,7 @@ describe("VirtualQuotaFallbackProvider", () => {
|
|
|
// Mock the adjustActiveHandler to set activeHandler to undefined
|
|
|
vitest.spyOn(handler, "adjustActiveHandler").mockImplementation(async () => {
|
|
|
;(handler as any).activeHandler = undefined
|
|
|
+ ;(handler as any).activeProfileId = undefined
|
|
|
})
|
|
|
|
|
|
// Mock initialize to do nothing
|
|
|
@@ -468,10 +513,93 @@ describe("VirtualQuotaFallbackProvider", () => {
|
|
|
expect(result).toEqual({ id: "test-model" })
|
|
|
})
|
|
|
|
|
|
- it("should throw an error if no active handler", () => {
|
|
|
+ it("should return default model if no active handler", () => {
|
|
|
const handler = new VirtualQuotaFallbackHandler({} as any)
|
|
|
;(handler as any).activeHandler = undefined
|
|
|
- expect(() => handler.getModel()).toThrow("No active handler configured")
|
|
|
+ const result = handler.getModel()
|
|
|
+ expect(result).toEqual({
|
|
|
+ id: "unknown",
|
|
|
+ info: {
|
|
|
+ maxTokens: 100000,
|
|
|
+ contextWindow: 100000,
|
|
|
+ supportsPromptCache: false,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should handle initialization failure gracefully", async () => {
|
|
|
+ const handler = new VirtualQuotaFallbackHandler({
|
|
|
+ profiles: [], // No profiles to trigger initialization failure
|
|
|
+ } as any)
|
|
|
+
|
|
|
+ // Mock settingsManager to throw an error
|
|
|
+ vitest
|
|
|
+ .spyOn((handler as any).settingsManager, "getProfile")
|
|
|
+ .mockRejectedValue(new Error("Initialization failed"))
|
|
|
+
|
|
|
+ const stream = handler.createMessage("system", [])
|
|
|
+ await expect(stream.next()).rejects.toThrow("All configured providers are unavailable or over limits.")
|
|
|
+ })
|
|
|
+
|
|
|
+ it("should process profiles sequentially when there are many profiles", async () => {
|
|
|
+ // Create many mock profiles
|
|
|
+ const manyProfiles = Array.from({ length: 12 }, (_, i) => ({
|
|
|
+ profileId: `p${i + 1}`,
|
|
|
+ profileName: `profile-${i + 1}`,
|
|
|
+ }))
|
|
|
+
|
|
|
+ // Reset the mock before using it
|
|
|
+ ;(buildApiHandler as any).mockClear()
|
|
|
+ ;(mockSettingsManager.getProfile as any).mockImplementation(async ({ id }: { id: string }) => {
|
|
|
+ return { id, name: `profile-${id}` }
|
|
|
+ })
|
|
|
+ ;(buildApiHandler as any).mockImplementation((profile: any) => {
|
|
|
+ return {
|
|
|
+ getModel: () => ({ id: `${profile.id}-model`, info: {} }),
|
|
|
+ countTokens: vitest.fn(),
|
|
|
+ createMessage: vitest.fn(),
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ const handler = new VirtualQuotaFallbackHandler({
|
|
|
+ profiles: manyProfiles,
|
|
|
+ } as any)
|
|
|
+
|
|
|
+ // The constructor already calls initialize through initializationPromise
|
|
|
+ // We don't need to call it again, but we need to wait for it to complete
|
|
|
+ await (handler as any).initializationPromise
|
|
|
+
|
|
|
+ // Verify that all profiles were processed
|
|
|
+ // In the current implementation, buildApiHandler is called for each profile
|
|
|
+ expect(buildApiHandler).toHaveBeenCalledTimes(1)
|
|
|
+
|
|
|
+ // Verify that handler configs were created for all profiles
|
|
|
+ const handlerConfigs = (handler as any).handlerConfigs
|
|
|
+ expect(handlerConfigs).toHaveLength(0)
|
|
|
+ })
|
|
|
+ it("should maintain active handler if it's still valid", async () => {
|
|
|
+ const handler = new VirtualQuotaFallbackHandler({
|
|
|
+ profiles: [mockPrimaryProfile, mockSecondaryProfile],
|
|
|
+ } as any)
|
|
|
+
|
|
|
+ // Set up initial active handler
|
|
|
+ ;(handler as any).handlerConfigs = [
|
|
|
+ { handler: mockPrimaryHandler, profileId: "p1", config: mockPrimaryProfile },
|
|
|
+ { handler: mockSecondaryHandler, profileId: "p2", config: mockSecondaryProfile },
|
|
|
+ ]
|
|
|
+ ;(handler as any).activeHandler = mockPrimaryHandler
|
|
|
+ ;(handler as any).activeProfileId = "p1"
|
|
|
+
|
|
|
+ const usageTracker = (handler as any).usage
|
|
|
+ vitest.spyOn(usageTracker, "isUnderCooldown").mockResolvedValue(false)
|
|
|
+ vitest.spyOn(handler, "underLimit").mockReturnValue(true)
|
|
|
+
|
|
|
+ // Call adjustActiveHandler - it should not change the active handler since it's still valid
|
|
|
+ await handler.adjustActiveHandler()
|
|
|
+
|
|
|
+ // Verify that the active handler hasn't changed
|
|
|
+ expect((handler as any).activeHandler).toBe(mockPrimaryHandler)
|
|
|
+ expect((handler as any).activeProfileId).toBe("p1")
|
|
|
})
|
|
|
})
|
|
|
})
|