From 24d2adb29882add7023f29fae9a156ec0d334fe9 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Fri, 18 Jul 2025 20:22:27 +0000 Subject: [PATCH 1/3] feat: add custom region input support for AWS Bedrock provider - Add awsCustomRegion field to ProviderSettings schema - Update BEDROCK_REGIONS to include "Custom region..." option - Modify Bedrock UI component to support custom region input - Add conditional UI similar to VPC endpoint pattern - Handle state management for switching between standard and custom regions - Preserve existing validation logic and maintain backward compatibility - Add comprehensive tests for custom region functionality Fixes #5923 --- packages/types/src/provider-settings.ts | 1 + packages/types/src/providers/bedrock.ts | 8 +- .../components/settings/providers/Bedrock.tsx | 32 ++- .../providers/__tests__/Bedrock.spec.tsx | 211 ++++++++++++++++++ 4 files changed, 250 insertions(+), 2 deletions(-) diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index be74ae6bb4c..b971e738344 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -107,6 +107,7 @@ const bedrockSchema = apiModelIdProviderModelSchema.extend({ awsSecretKey: z.string().optional(), awsSessionToken: z.string().optional(), awsRegion: z.string().optional(), + awsCustomRegion: z.string().optional(), awsUseCrossRegionInference: z.boolean().optional(), awsUsePromptCache: z.boolean().optional(), awsProfile: z.string().optional(), diff --git a/packages/types/src/providers/bedrock.ts b/packages/types/src/providers/bedrock.ts index 58e860dd94a..be686ce8442 100644 --- a/packages/types/src/providers/bedrock.ts +++ b/packages/types/src/providers/bedrock.ts @@ -405,4 +405,10 @@ export const BEDROCK_REGIONS = [ { value: "sa-east-1", label: "sa-east-1" }, { value: "us-gov-east-1", label: "us-gov-east-1" }, { value: "us-gov-west-1", label: "us-gov-west-1" }, -].sort((a, b) => a.value.localeCompare(b.value)) + { value: "custom", label: "Custom region..." }, +].sort((a, b) => { + // Keep "Custom region..." at the end + if (a.value === "custom") return 1 + if (b.value === "custom") return -1 + return a.value.localeCompare(b.value) +}) diff --git a/webview-ui/src/components/settings/providers/Bedrock.tsx b/webview-ui/src/components/settings/providers/Bedrock.tsx index 1839298f9b6..86bb2251113 100644 --- a/webview-ui/src/components/settings/providers/Bedrock.tsx +++ b/webview-ui/src/components/settings/providers/Bedrock.tsx @@ -18,12 +18,18 @@ type BedrockProps = { export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedModelInfo }: BedrockProps) => { const { t } = useAppTranslation() const [awsEndpointSelected, setAwsEndpointSelected] = useState(!!apiConfiguration?.awsBedrockEndpointEnabled) + const [customRegionSelected, setCustomRegionSelected] = useState(apiConfiguration?.awsRegion === "custom") // Update the endpoint enabled state when the configuration changes useEffect(() => { setAwsEndpointSelected(!!apiConfiguration?.awsBedrockEndpointEnabled) }, [apiConfiguration?.awsBedrockEndpointEnabled]) + // Update the custom region state when the configuration changes + useEffect(() => { + setCustomRegionSelected(apiConfiguration?.awsRegion === "custom") + }, [apiConfiguration?.awsRegion]) + const handleInputChange = useCallback( ( field: K, @@ -89,7 +95,14 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo + {customRegionSelected && ( + <> + +
+ {t("settings:providers.awsCustomRegion.examples")} +
• us-west-3
+
• eu-central-3
+
• ap-southeast-3
+
+ + )} diff --git a/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx b/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx index b5bb16e9751..80c1c4fd901 100644 --- a/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx +++ b/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx @@ -425,4 +425,215 @@ describe("Bedrock Component", () => { expect(screen.getByTestId("vpc-endpoint-input")).toHaveValue("https://updated-endpoint.aws.com") }) }) + + // Test Scenario 6: Custom Region Tests + describe("Custom Region", () => { + it("should show custom region input when 'Custom region...' is selected", () => { + const apiConfiguration: Partial = { + awsRegion: "", + awsUseProfile: true, + } + + render( + , + ) + + // Custom region input should not be visible initially + expect(screen.queryByTestId("custom-region-input")).not.toBeInTheDocument() + + // Mock the Select component to simulate selecting "custom" + // Since we're mocking the Select component, we need to simulate the onValueChange call + const selectComponent = screen.getByRole("combobox", { hidden: true }) + if (selectComponent) { + // Simulate selecting "custom" region + const onValueChange = (selectComponent as any).onValueChange || (() => {}) + onValueChange("custom") + } + + // Verify that setApiConfigurationField was called with "custom" + expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsRegion", "custom") + }) + + it("should hide custom region input when switching from custom to standard region", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "us-west-3", + awsUseProfile: true, + } + + render( + , + ) + + // Custom region input should be visible initially + expect(screen.getByTestId("custom-region-input")).toBeInTheDocument() + expect(screen.getByTestId("custom-region-input")).toHaveValue("us-west-3") + + // Mock selecting a standard region + const selectComponent = screen.getByRole("combobox", { hidden: true }) + if (selectComponent) { + const onValueChange = (selectComponent as any).onValueChange || (() => {}) + onValueChange("us-east-1") + } + + // Verify that both awsRegion and awsCustomRegion were updated + expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsRegion", "us-east-1") + expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsCustomRegion", "") + }) + + it("should handle custom region input changes", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "", + awsUseProfile: true, + } + + render( + , + ) + + // Find the custom region input field + const customRegionInput = screen.getByTestId("custom-region-input") + expect(customRegionInput).toBeInTheDocument() + + // Enter a custom region + fireEvent.change(customRegionInput, { target: { value: "us-west-3" } }) + + // Verify the configuration field was updated + expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsCustomRegion", "us-west-3") + }) + + it("should display example regions when custom region is selected", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "us-west-3", + awsUseProfile: true, + } + + render( + , + ) + + // Check that the custom region input is visible + expect(screen.getByTestId("custom-region-input")).toBeInTheDocument() + + // Check for the example regions section + expect(screen.getByText("settings:providers.awsCustomRegion.examples")).toBeInTheDocument() + expect(screen.getByText("• us-west-3")).toBeInTheDocument() + expect(screen.getByText("• eu-central-3")).toBeInTheDocument() + expect(screen.getByText("• ap-southeast-3")).toBeInTheDocument() + }) + + it("should preserve custom region value when toggling between custom and standard regions", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "us-west-3", + awsUseProfile: true, + } + + const { rerender } = render( + , + ) + + // Initial state: custom region selected with value + expect(screen.getByTestId("custom-region-input")).toBeInTheDocument() + expect(screen.getByTestId("custom-region-input")).toHaveValue("us-west-3") + + // Switch to standard region + const updatedConfig: Partial = { + awsRegion: "us-east-1", + awsCustomRegion: "", // This would be cleared by the component logic + awsUseProfile: true, + } + + rerender( + , + ) + + // Custom region input should be hidden + expect(screen.queryByTestId("custom-region-input")).not.toBeInTheDocument() + + // Switch back to custom region with preserved value + const backToCustomConfig: Partial = { + awsRegion: "custom", + awsCustomRegion: "us-west-3", // Value preserved in parent state + awsUseProfile: true, + } + + rerender( + , + ) + + // Custom region input should be visible again with preserved value + expect(screen.getByTestId("custom-region-input")).toBeInTheDocument() + expect(screen.getByTestId("custom-region-input")).toHaveValue("us-west-3") + }) + + it("should handle empty custom region input", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "us-west-3", + awsUseProfile: true, + } + + render( + , + ) + + // Find the custom region input field + const customRegionInput = screen.getByTestId("custom-region-input") + + // Clear the field + fireEvent.change(customRegionInput, { target: { value: "" } }) + + // Verify the configuration field was updated with empty string + expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsCustomRegion", "") + }) + + it("should initialize with correct state when custom region is pre-selected", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "eu-central-3", + awsUseProfile: true, + } + + render( + , + ) + + // Verify custom region input is visible and has correct value + expect(screen.getByTestId("custom-region-input")).toBeInTheDocument() + expect(screen.getByTestId("custom-region-input")).toHaveValue("eu-central-3") + + // Verify examples are shown + expect(screen.getByText("settings:providers.awsCustomRegion.examples")).toBeInTheDocument() + }) + }) }) From 9008f16cca3af82ca0de60b99ec7681b36271b28 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 22 Jul 2025 17:54:50 +0000 Subject: [PATCH 2/3] fix: implement backend support for custom AWS regions - Backend now properly uses awsCustomRegion when awsRegion is 'custom' - Added i18n translations for custom region UI elements - Added validation for AWS region format (e.g., us-west-3) - Improved state management to preserve custom region value when switching - Added comprehensive tests for custom region functionality --- .../__tests__/bedrock-custom-region.spec.ts | 107 +++++++++ src/api/providers/bedrock.ts | 24 ++- .../components/settings/providers/Bedrock.tsx | 50 ++++- .../providers/__tests__/Bedrock.spec.tsx | 203 +++++++++++++++++- webview-ui/src/i18n/locales/en/settings.json | 9 + 5 files changed, 380 insertions(+), 13 deletions(-) create mode 100644 src/api/providers/__tests__/bedrock-custom-region.spec.ts diff --git a/src/api/providers/__tests__/bedrock-custom-region.spec.ts b/src/api/providers/__tests__/bedrock-custom-region.spec.ts new file mode 100644 index 00000000000..522d01c1b26 --- /dev/null +++ b/src/api/providers/__tests__/bedrock-custom-region.spec.ts @@ -0,0 +1,107 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import { AwsBedrockHandler } from "../bedrock" +import { BedrockRuntimeClient } from "@aws-sdk/client-bedrock-runtime" + +// Mock the AWS SDK +vi.mock("@aws-sdk/client-bedrock-runtime", () => ({ + BedrockRuntimeClient: vi.fn().mockImplementation((config) => ({ + config, + send: vi.fn(), + })), + ConverseCommand: vi.fn(), + ConverseStreamCommand: vi.fn(), +})) + +describe("AwsBedrockHandler - Custom Region Support", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("should use custom region when awsRegion is 'custom' and awsCustomRegion is provided", () => { + const handler = new AwsBedrockHandler({ + apiProvider: "bedrock", + apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0", + awsAccessKey: "test-access-key", + awsSecretKey: "test-secret-key", + awsRegion: "custom", + awsCustomRegion: "us-west-3", + }) + + // Get the mock instance to check the config + const mockClientInstance = vi.mocked(BedrockRuntimeClient).mock.results[0]?.value + expect(mockClientInstance.config.region).toBe("us-west-3") + }) + + it("should use standard region when awsRegion is not 'custom'", () => { + const handler = new AwsBedrockHandler({ + apiProvider: "bedrock", + apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0", + awsAccessKey: "test-access-key", + awsSecretKey: "test-secret-key", + awsRegion: "us-east-1", + awsCustomRegion: "us-west-3", // This should be ignored + }) + + // Get the mock instance to check the config + const mockClientInstance = vi.mocked(BedrockRuntimeClient).mock.results[0]?.value + expect(mockClientInstance.config.region).toBe("us-east-1") + }) + + it("should use awsRegion when awsCustomRegion is not provided", () => { + const handler = new AwsBedrockHandler({ + apiProvider: "bedrock", + apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0", + awsAccessKey: "test-access-key", + awsSecretKey: "test-secret-key", + awsRegion: "custom", + // awsCustomRegion is not provided + }) + + // Get the mock instance to check the config + const mockClientInstance = vi.mocked(BedrockRuntimeClient).mock.results[0]?.value + expect(mockClientInstance.config.region).toBe("custom") + }) + + it("should use custom region for cross-region inference prefix calculation", () => { + const handler = new AwsBedrockHandler({ + apiProvider: "bedrock", + apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0", + awsAccessKey: "test-access-key", + awsSecretKey: "test-secret-key", + awsRegion: "custom", + awsCustomRegion: "us-west-3", + awsUseCrossRegionInference: true, + }) + + const model = handler.getModel() + // Should have the us. prefix for us-west-3 + expect(model.id).toContain("us.") + }) + + it("should handle custom regions with different prefixes for cross-region inference", () => { + const testCases = [ + { customRegion: "eu-central-3", expectedPrefix: "eu." }, + { customRegion: "ap-southeast-4", expectedPrefix: "apac." }, + { customRegion: "ca-west-1", expectedPrefix: "ca." }, + { customRegion: "sa-east-2", expectedPrefix: "sa." }, + { customRegion: "us-gov-west-2", expectedPrefix: "ug." }, + ] + + for (const { customRegion, expectedPrefix } of testCases) { + vi.clearAllMocks() + + const handler = new AwsBedrockHandler({ + apiProvider: "bedrock", + apiModelId: "anthropic.claude-3-sonnet-20240229-v1:0", + awsAccessKey: "test-access-key", + awsSecretKey: "test-secret-key", + awsRegion: "custom", + awsCustomRegion: customRegion, + awsUseCrossRegionInference: true, + }) + + const model = handler.getModel() + expect(model.id).toContain(expectedPrefix) + } + }) +}) diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index a25fea52003..3ace00f5258 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -169,7 +169,11 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH constructor(options: ProviderSettings) { super() this.options = options - let region = this.options.awsRegion + // Use custom region if awsRegion is "custom" + let region = + this.options.awsRegion === "custom" && this.options.awsCustomRegion + ? this.options.awsCustomRegion + : this.options.awsRegion // process the various user input options, be opinionated about the intent of the options // and determine the model to use during inference and for cost calculations @@ -216,7 +220,7 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH this.costModelConfig = this.getModel() const clientConfig: BedrockRuntimeClientConfig = { - region: this.options.awsRegion, + region: region, // Use the resolved region (either standard or custom) // Add the endpoint configuration when specified and enabled ...(this.options.awsBedrockEndpoint && this.options.awsBedrockEndpointEnabled && { endpoint: this.options.awsBedrockEndpoint }), @@ -943,10 +947,18 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH modelConfig = this.getModelById(this.options.apiModelId as string) // Add cross-region inference prefix if enabled - if (this.options.awsUseCrossRegionInference && this.options.awsRegion) { - const prefix = AwsBedrockHandler.getPrefixForRegion(this.options.awsRegion) - if (prefix) { - modelConfig.id = `${prefix}${modelConfig.id}` + if (this.options.awsUseCrossRegionInference) { + // Use custom region if awsRegion is "custom" + const regionToUse = + this.options.awsRegion === "custom" && this.options.awsCustomRegion + ? this.options.awsCustomRegion + : this.options.awsRegion + + if (regionToUse) { + const prefix = AwsBedrockHandler.getPrefixForRegion(regionToUse) + if (prefix) { + modelConfig.id = `${prefix}${modelConfig.id}` + } } } } diff --git a/webview-ui/src/components/settings/providers/Bedrock.tsx b/webview-ui/src/components/settings/providers/Bedrock.tsx index 86bb2251113..1b51ea594e1 100644 --- a/webview-ui/src/components/settings/providers/Bedrock.tsx +++ b/webview-ui/src/components/settings/providers/Bedrock.tsx @@ -9,6 +9,9 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Standard import { inputEventTransform, noTransform } from "../transforms" +// AWS region format validation regex +const AWS_REGION_REGEX = /^[a-z]{2,}-[a-z]+-\d+$/ + type BedrockProps = { apiConfiguration: ProviderSettings setApiConfigurationField: (field: keyof ProviderSettings, value: ProviderSettings[keyof ProviderSettings]) => void @@ -19,6 +22,7 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo const { t } = useAppTranslation() const [awsEndpointSelected, setAwsEndpointSelected] = useState(!!apiConfiguration?.awsBedrockEndpointEnabled) const [customRegionSelected, setCustomRegionSelected] = useState(apiConfiguration?.awsRegion === "custom") + const [customRegionError, setCustomRegionError] = useState(null) // Update the endpoint enabled state when the configuration changes useEffect(() => { @@ -41,6 +45,34 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo [setApiConfigurationField], ) + // Validate custom region format + const validateCustomRegion = useCallback( + (value: string) => { + if (!value && customRegionSelected) { + setCustomRegionError(t("settings:providers.awsCustomRegion.validation.required")) + return false + } + if (value && !AWS_REGION_REGEX.test(value)) { + setCustomRegionError(t("settings:providers.awsCustomRegion.validation.format")) + return false + } + setCustomRegionError(null) + return true + }, + [customRegionSelected, t], + ) + + // Handle custom region input change with validation + const handleCustomRegionChange = useCallback( + (event: Event | React.FormEvent) => { + const target = event.target as HTMLInputElement + const value = target.value + validateCustomRegion(value) + setApiConfigurationField("awsCustomRegion", value) + }, + [setApiConfigurationField, validateCustomRegion], + ) + return ( <> { setApiConfigurationField("awsRegion", value) setCustomRegionSelected(value === "custom") - // Clear custom region when switching to a standard region - if (value !== "custom") { - setApiConfigurationField("awsCustomRegion", "") + // Don't clear custom region when switching away - preserve the value + if (value === "custom" && apiConfiguration?.awsCustomRegion) { + // Validate the existing custom region value + validateCustomRegion(apiConfiguration.awsCustomRegion) + } else { + // Clear validation error when not using custom region + setCustomRegionError(null) } }}> @@ -120,10 +156,14 @@ export const Bedrock = ({ apiConfiguration, setApiConfigurationField, selectedMo + {customRegionError && ( +
{customRegionError}
+ )}
{t("settings:providers.awsCustomRegion.examples")}
• us-west-3
diff --git a/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx b/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx index 80c1c4fd901..a2f6774563c 100644 --- a/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx +++ b/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx @@ -61,11 +61,18 @@ vi.mock("@src/i18n/TranslationContext", () => ({ // Mock the UI components vi.mock("@src/components/ui", () => ({ - Select: ({ children }: any) =>
{children}
, + Select: ({ children, onValueChange }: any) => { + // Store the onValueChange callback on the window for testing + if (typeof window !== "undefined") { + ;(window as any).__selectOnValueChange = onValueChange + } + return
{children}
+ }, SelectContent: ({ children }: any) =>
{children}
, SelectItem: () =>
Item
, - SelectTrigger: ({ children }: any) =>
{children}
, + SelectTrigger: ({ children }: any) =>
{children}
, SelectValue: () =>
Value
, + StandardTooltip: ({ children }: any) =>
{children}
, })) // Mock the constants @@ -636,4 +643,196 @@ describe("Bedrock Component", () => { expect(screen.getByText("settings:providers.awsCustomRegion.examples")).toBeInTheDocument() }) }) + + // Test Scenario 7: Custom Region Validation Tests + describe("Custom Region Validation", () => { + it("should show validation error when custom region is empty", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "", + awsUseProfile: true, + } + + render( + , + ) + + // The custom region input should be visible + const customRegionInput = screen.getByTestId("custom-region-input") + expect(customRegionInput).toBeInTheDocument() + + // Trigger validation by changing input + fireEvent.change(customRegionInput, { target: { value: "" } }) + + // Should show required validation error + expect(screen.getByText("settings:providers.awsCustomRegion.validation.required")).toBeInTheDocument() + }) + + it("should show validation error for invalid region format", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "", + awsUseProfile: true, + } + + render( + , + ) + + const customRegionInput = screen.getByTestId("custom-region-input") + + // Test various invalid formats + const invalidRegions = [ + "invalid-region", + "us-west", + "us-3", + "uswest3", + "US-WEST-3", + "us-west-three", + "123-west-3", + ] + + for (const invalidRegion of invalidRegions) { + fireEvent.change(customRegionInput, { target: { value: invalidRegion } }) + + // Should show format validation error + expect(screen.getByText("settings:providers.awsCustomRegion.validation.format")).toBeInTheDocument() + } + }) + + it("should accept valid region formats", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "", + awsUseProfile: true, + } + + render( + , + ) + + const customRegionInput = screen.getByTestId("custom-region-input") + + // Test various valid formats + const validRegions = [ + "us-west-3", + "eu-central-2", + "ap-southeast-4", + "sa-east-2", + "ca-west-1", + "me-south-2", + "af-south-1", + ] + + for (const validRegion of validRegions) { + fireEvent.change(customRegionInput, { target: { value: validRegion } }) + + // Should update the field + expect(mockSetApiConfigurationField).toHaveBeenCalledWith("awsCustomRegion", validRegion) + + // Should not show any error + expect( + screen.queryByText("settings:providers.awsCustomRegion.validation.format"), + ).not.toBeInTheDocument() + expect( + screen.queryByText("settings:providers.awsCustomRegion.validation.required"), + ).not.toBeInTheDocument() + } + }) + + it("should preserve custom region value when switching between regions", () => { + const apiConfiguration: Partial = { + awsRegion: "custom", + awsCustomRegion: "us-west-3", + awsUseProfile: true, + } + + const { rerender } = render( + , + ) + + // Custom region input should be visible with the value + let customRegionInput = screen.getByTestId("custom-region-input") as HTMLInputElement + expect(customRegionInput.value).toBe("us-west-3") + + // Switch to a standard region by calling the onValueChange directly + if ((window as any).__selectOnValueChange) { + ;(window as any).__selectOnValueChange("us-east-1") + } + + // Update the configuration + apiConfiguration.awsRegion = "us-east-1" + rerender( + , + ) + + // Custom region input should be hidden + expect(screen.queryByTestId("custom-region-input")).not.toBeInTheDocument() + + // Switch back to custom region + if ((window as any).__selectOnValueChange) { + ;(window as any).__selectOnValueChange("custom") + } + + // Update the configuration + apiConfiguration.awsRegion = "custom" + rerender( + , + ) + + // Custom region input should be visible again with the preserved value + customRegionInput = screen.getByTestId("custom-region-input") as HTMLInputElement + expect(customRegionInput.value).toBe("us-west-3") + }) + + it("should validate existing custom region when switching back to custom", () => { + const apiConfiguration: Partial = { + awsRegion: "us-east-1", + awsCustomRegion: "invalid-region", // Invalid format + awsUseProfile: true, + } + + const { rerender } = render( + , + ) + + // Switch to custom region + if ((window as any).__selectOnValueChange) { + ;(window as any).__selectOnValueChange("custom") + } + + // Update the configuration + apiConfiguration.awsRegion = "custom" + rerender( + , + ) + + // Should show validation error for the existing invalid value + expect(screen.getByText("settings:providers.awsCustomRegion.validation.format")).toBeInTheDocument() + }) + }) }) diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 728e8565020..29b8ea497bb 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -273,6 +273,14 @@ "awsSessionToken": "AWS Session Token", "awsRegion": "AWS Region", "awsCrossRegion": "Use cross-region inference", + "awsCustomRegion": { + "placeholder": "Enter custom region (e.g., us-west-3)", + "examples": "Examples of new or custom regions:", + "validation": { + "required": "Custom region is required when 'Custom region...' is selected", + "format": "Region must be in format: region-direction-number (e.g., us-west-3, eu-central-2)" + } + }, "awsBedrockVpc": { "useCustomVpcEndpoint": "Use custom VPC endpoint", "vpcEndpointUrlPlaceholder": "Enter VPC Endpoint URL (optional)", @@ -686,6 +694,7 @@ "keyFilePath": "Enter Key File Path...", "projectId": "Enter Project ID...", "customArn": "Enter ARN (e.g. arn:aws:bedrock:us-east-1:123456789012:foundation-model/my-model)", + "customRegion": "Enter custom region (e.g., us-west-3)", "baseUrl": "Enter base URL...", "modelId": { "lmStudio": "e.g. meta-llama-3.1-8b-instruct", From 4a563573e67b782cbbf5f3fc934fb17fb32a63c7 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 22 Jul 2025 17:55:49 +0000 Subject: [PATCH 3/3] fix: update Bedrock tests to handle mocked Select component properly --- .../providers/__tests__/Bedrock.spec.tsx | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx b/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx index a2f6774563c..9b5e816bb77 100644 --- a/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx +++ b/webview-ui/src/components/settings/providers/__tests__/Bedrock.spec.tsx @@ -59,17 +59,29 @@ vi.mock("@src/i18n/TranslationContext", () => ({ }), })) +// Store mock callbacks globally for test access +const mockSelectCallbacks: { [key: string]: any } = {} + // Mock the UI components vi.mock("@src/components/ui", () => ({ - Select: ({ children, onValueChange }: any) => { - // Store the onValueChange callback on the window for testing + Select: ({ children, onValueChange, value }: any) => { + // Store the onValueChange callback on the window for testing (for new tests) if (typeof window !== "undefined") { ;(window as any).__selectOnValueChange = onValueChange } - return
{children}
+ // Also store the callback for test access (for existing tests) + if (onValueChange) { + mockSelectCallbacks.onValueChange = onValueChange + } + + return ( +
+ {children} +
+ ) }, SelectContent: ({ children }: any) =>
{children}
, - SelectItem: () =>
Item
, + SelectItem: ({ value }: any) =>
Item
, SelectTrigger: ({ children }: any) =>
{children}
, SelectValue: () =>
Value
, StandardTooltip: ({ children }: any) =>
{children}
, @@ -451,13 +463,12 @@ describe("Bedrock Component", () => { // Custom region input should not be visible initially expect(screen.queryByTestId("custom-region-input")).not.toBeInTheDocument() - // Mock the Select component to simulate selecting "custom" - // Since we're mocking the Select component, we need to simulate the onValueChange call - const selectComponent = screen.getByRole("combobox", { hidden: true }) - if (selectComponent) { - // Simulate selecting "custom" region - const onValueChange = (selectComponent as any).onValueChange || (() => {}) - onValueChange("custom") + // Verify the Select component is rendered + expect(screen.getByTestId("select-component")).toBeInTheDocument() + + // Call the onValueChange callback directly + if (mockSelectCallbacks.onValueChange) { + mockSelectCallbacks.onValueChange("custom") } // Verify that setApiConfigurationField was called with "custom" @@ -482,11 +493,12 @@ describe("Bedrock Component", () => { expect(screen.getByTestId("custom-region-input")).toBeInTheDocument() expect(screen.getByTestId("custom-region-input")).toHaveValue("us-west-3") - // Mock selecting a standard region - const selectComponent = screen.getByRole("combobox", { hidden: true }) - if (selectComponent) { - const onValueChange = (selectComponent as any).onValueChange || (() => {}) - onValueChange("us-east-1") + // Verify the Select component is rendered + expect(screen.getByTestId("select-component")).toBeInTheDocument() + + // Call the onValueChange callback directly + if (mockSelectCallbacks.onValueChange) { + mockSelectCallbacks.onValueChange("us-east-1") } // Verify that both awsRegion and awsCustomRegion were updated