mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-09 03:17:58 +00:00
Merge pull request #712 from samhvw8/fix/api-config-error-when-switch-provider
fix(api-config) error when creation of api config
This commit is contained in:
commit
053a9400fd
3 changed files with 152 additions and 9 deletions
|
|
@ -1273,4 +1273,129 @@ describe("ClineProvider", () => {
|
|||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("upsertApiConfiguration", () => {
|
||||
test("handles error in upsertApiConfiguration gracefully", async () => {
|
||||
provider.resolveWebviewView(mockWebviewView)
|
||||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
|
||||
|
||||
// Mock ConfigManager methods to simulate error
|
||||
provider.configManager = {
|
||||
setModeConfig: jest.fn().mockRejectedValue(new Error("Failed to update mode config")),
|
||||
listConfig: jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]),
|
||||
} as any
|
||||
|
||||
// Mock getState to provide necessary data
|
||||
jest.spyOn(provider, "getState").mockResolvedValue({
|
||||
mode: "code",
|
||||
currentApiConfigName: "test-config",
|
||||
} as any)
|
||||
|
||||
// Trigger updateApiConfiguration
|
||||
await messageHandler({
|
||||
type: "upsertApiConfiguration",
|
||||
text: "test-config",
|
||||
apiConfiguration: {
|
||||
apiProvider: "anthropic",
|
||||
apiKey: "test-key",
|
||||
},
|
||||
})
|
||||
|
||||
// Verify error was logged and user was notified
|
||||
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Error create new api configuration"),
|
||||
)
|
||||
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Failed to create api configuration")
|
||||
})
|
||||
|
||||
test("handles successful upsertApiConfiguration", async () => {
|
||||
provider.resolveWebviewView(mockWebviewView)
|
||||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
|
||||
|
||||
// Mock ConfigManager methods
|
||||
provider.configManager = {
|
||||
saveConfig: jest.fn().mockResolvedValue(undefined),
|
||||
listConfig: jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]),
|
||||
} as any
|
||||
|
||||
const testApiConfig = {
|
||||
apiProvider: "anthropic" as const,
|
||||
apiKey: "test-key",
|
||||
}
|
||||
|
||||
// Trigger upsertApiConfiguration
|
||||
await messageHandler({
|
||||
type: "upsertApiConfiguration",
|
||||
text: "test-config",
|
||||
apiConfiguration: testApiConfig,
|
||||
})
|
||||
|
||||
// Verify config was saved
|
||||
expect(provider.configManager.saveConfig).toHaveBeenCalledWith("test-config", testApiConfig)
|
||||
|
||||
// Verify state updates
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("listApiConfigMeta", [
|
||||
{ name: "test-config", id: "test-id", apiProvider: "anthropic" },
|
||||
])
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "test-config")
|
||||
|
||||
// Verify state was posted to webview
|
||||
expect(mockPostMessage).toHaveBeenCalledWith(expect.objectContaining({ type: "state" }))
|
||||
})
|
||||
|
||||
test("handles buildApiHandler error in updateApiConfiguration", async () => {
|
||||
provider.resolveWebviewView(mockWebviewView)
|
||||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
|
||||
|
||||
// Mock buildApiHandler to throw an error
|
||||
const { buildApiHandler } = require("../../../api")
|
||||
;(buildApiHandler as jest.Mock).mockImplementationOnce(() => {
|
||||
throw new Error("API handler error")
|
||||
})
|
||||
|
||||
// Mock ConfigManager methods
|
||||
provider.configManager = {
|
||||
saveConfig: jest.fn().mockResolvedValue(undefined),
|
||||
listConfig: jest
|
||||
.fn()
|
||||
.mockResolvedValue([{ name: "test-config", id: "test-id", apiProvider: "anthropic" }]),
|
||||
} as any
|
||||
|
||||
// Setup mock Cline instance
|
||||
const mockCline = {
|
||||
api: undefined,
|
||||
abortTask: jest.fn(),
|
||||
}
|
||||
// @ts-ignore - accessing private property for testing
|
||||
provider.cline = mockCline
|
||||
|
||||
const testApiConfig = {
|
||||
apiProvider: "anthropic" as const,
|
||||
apiKey: "test-key",
|
||||
}
|
||||
|
||||
// Trigger upsertApiConfiguration
|
||||
await messageHandler({
|
||||
type: "upsertApiConfiguration",
|
||||
text: "test-config",
|
||||
apiConfiguration: testApiConfig,
|
||||
})
|
||||
|
||||
// Verify error handling
|
||||
expect(mockOutputChannel.appendLine).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Error create new api configuration"),
|
||||
)
|
||||
expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Failed to create api configuration")
|
||||
|
||||
// Verify state was still updated
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("listApiConfigMeta", [
|
||||
{ name: "test-config", id: "test-id", apiProvider: "anthropic" },
|
||||
])
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "test-config")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -128,7 +128,10 @@ const ApiOptions = ({ apiErrorMessage, modelIdErrorMessage }: ApiOptionsProps) =
|
|||
id="api-provider"
|
||||
value={selectedProvider}
|
||||
onChange={(value: unknown) => {
|
||||
handleInputChange("apiProvider")({
|
||||
handleInputChange(
|
||||
"apiProvider",
|
||||
true,
|
||||
)({
|
||||
target: {
|
||||
value: (value as DropdownOption).value,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||
setEnhancementApiConfigId: (value: string) => void
|
||||
setExperimentEnabled: (id: ExperimentId, enabled: boolean) => void
|
||||
setAutoApprovalEnabled: (value: boolean) => void
|
||||
handleInputChange: (field: keyof ApiConfiguration) => (event: any) => void
|
||||
handleInputChange: (field: keyof ApiConfiguration, softUpdate?: boolean) => (event: any) => void
|
||||
customModes: ModeConfig[]
|
||||
setCustomModes: (value: ModeConfig[]) => void
|
||||
}
|
||||
|
|
@ -142,14 +142,29 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
}, [])
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(field: keyof ApiConfiguration) => (event: any) => {
|
||||
// Returns a function that handles an input change event for a specific API configuration field.
|
||||
// The optional "softUpdate" flag determines whether to immediately update local state or send an external update.
|
||||
(field: keyof ApiConfiguration, softUpdate?: boolean) => (event: any) => {
|
||||
// Use the functional form of setState to ensure the latest state is used in the update logic.
|
||||
setState((currentState) => {
|
||||
vscode.postMessage({
|
||||
type: "upsertApiConfiguration",
|
||||
text: currentState.currentApiConfigName,
|
||||
apiConfiguration: { ...currentState.apiConfiguration, [field]: event.target.value },
|
||||
})
|
||||
return currentState // No state update needed
|
||||
if (softUpdate) {
|
||||
// Return a new state object with the updated apiConfiguration.
|
||||
// This will trigger a re-render with the new configuration value.
|
||||
return {
|
||||
...currentState,
|
||||
apiConfiguration: { ...currentState.apiConfiguration, [field]: event.target.value },
|
||||
}
|
||||
} else {
|
||||
// For non-soft updates, send a message to the VS Code extension with the updated config.
|
||||
// This side effect communicates the change without updating local React state.
|
||||
vscode.postMessage({
|
||||
type: "upsertApiConfiguration",
|
||||
text: currentState.currentApiConfigName,
|
||||
apiConfiguration: { ...currentState.apiConfiguration, [field]: event.target.value },
|
||||
})
|
||||
// Return the unchanged state as no local state update is intended in this branch.
|
||||
return currentState
|
||||
}
|
||||
})
|
||||
},
|
||||
[],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue