mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-16 04:28:38 +00:00
298 lines
9.3 KiB
TypeScript
298 lines
9.3 KiB
TypeScript
import type { ProviderSettings, OrganizationAllowList, RouterModels } from "@roo-code/types"
|
|
|
|
// Mock i18next to return translation keys with interpolated values
|
|
vi.mock("i18next", () => ({
|
|
default: {
|
|
t: (key: string, options?: Record<string, string>) => {
|
|
if (options) {
|
|
let result = key
|
|
Object.entries(options).forEach(([k, v]) => {
|
|
result += ` ${k}=${v}`
|
|
})
|
|
return result
|
|
}
|
|
return key
|
|
},
|
|
},
|
|
}))
|
|
|
|
import { getModelValidationError, validateApiConfigurationExcludingModelErrors, validateBedrockArn } from "../validate"
|
|
|
|
describe("Model Validation Functions", () => {
|
|
const mockRouterModels: RouterModels = {
|
|
openrouter: {
|
|
"valid-model": {
|
|
maxTokens: 8192,
|
|
contextWindow: 200000,
|
|
supportsImages: true,
|
|
supportsPromptCache: false,
|
|
inputPrice: 3.0,
|
|
outputPrice: 15.0,
|
|
},
|
|
"another-valid-model": {
|
|
maxTokens: 4096,
|
|
contextWindow: 100000,
|
|
supportsImages: false,
|
|
supportsPromptCache: false,
|
|
inputPrice: 1.0,
|
|
outputPrice: 5.0,
|
|
},
|
|
},
|
|
requesty: {},
|
|
unbound: {},
|
|
litellm: {},
|
|
ollama: {},
|
|
lmstudio: {},
|
|
"vercel-ai-gateway": {},
|
|
roo: {},
|
|
poe: {},
|
|
}
|
|
|
|
const allowAllOrganization: OrganizationAllowList = {
|
|
allowAll: true,
|
|
providers: {},
|
|
}
|
|
|
|
const restrictiveOrganization: OrganizationAllowList = {
|
|
allowAll: false,
|
|
providers: {
|
|
openrouter: {
|
|
allowAll: false,
|
|
models: ["valid-model"],
|
|
},
|
|
},
|
|
}
|
|
|
|
describe("getModelValidationError", () => {
|
|
it("returns undefined for valid OpenRouter model", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openrouter",
|
|
openRouterModelId: "valid-model",
|
|
}
|
|
|
|
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("returns error for invalid OpenRouter model", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openrouter",
|
|
openRouterModelId: "invalid-model",
|
|
}
|
|
|
|
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toContain("settings:validation.modelAvailability")
|
|
})
|
|
|
|
it("returns error for model not allowed by organization", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openrouter",
|
|
openRouterModelId: "another-valid-model",
|
|
}
|
|
|
|
const result = getModelValidationError(config, mockRouterModels, restrictiveOrganization)
|
|
expect(result).toContain("model")
|
|
})
|
|
|
|
it("returns undefined for OpenAI models when no router models provided", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openai",
|
|
openAiModelId: "gpt-4",
|
|
}
|
|
|
|
const result = getModelValidationError(config, undefined, allowAllOrganization)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("handles empty model IDs gracefully", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openrouter",
|
|
openRouterModelId: "",
|
|
}
|
|
|
|
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toBe("settings:validation.modelId")
|
|
})
|
|
|
|
it("handles undefined model IDs gracefully", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openrouter",
|
|
// openRouterModelId is undefined
|
|
}
|
|
|
|
const result = getModelValidationError(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toBe("settings:validation.modelId")
|
|
})
|
|
})
|
|
|
|
describe("validateApiConfigurationExcludingModelErrors", () => {
|
|
it("returns undefined when configuration is valid", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openrouter",
|
|
openRouterApiKey: "valid-key",
|
|
openRouterModelId: "valid-model",
|
|
}
|
|
|
|
const result = validateApiConfigurationExcludingModelErrors(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("returns error for missing API key", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openrouter",
|
|
openRouterModelId: "valid-model",
|
|
// Missing openRouterApiKey
|
|
}
|
|
|
|
const result = validateApiConfigurationExcludingModelErrors(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toBe("settings:validation.apiKey")
|
|
})
|
|
|
|
it("excludes model-specific errors", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openrouter",
|
|
openRouterApiKey: "valid-key",
|
|
openRouterModelId: "invalid-model", // This should be ignored
|
|
}
|
|
|
|
const result = validateApiConfigurationExcludingModelErrors(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toBeUndefined() // Should not return model validation error
|
|
})
|
|
|
|
it("returns undefined for litellm with base URL but no API key", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "litellm",
|
|
litellmBaseUrl: "http://localhost:4000",
|
|
// No litellmApiKey - should be valid for self-hosted instances
|
|
}
|
|
|
|
const result = validateApiConfigurationExcludingModelErrors(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("returns error for litellm without base URL", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "litellm",
|
|
// No litellmBaseUrl
|
|
}
|
|
|
|
const result = validateApiConfigurationExcludingModelErrors(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toBe("settings:validation.baseUrl")
|
|
})
|
|
|
|
it("returns undefined for litellm with both base URL and API key", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "litellm",
|
|
litellmBaseUrl: "http://localhost:4000",
|
|
litellmApiKey: "some-key",
|
|
}
|
|
|
|
const result = validateApiConfigurationExcludingModelErrors(config, mockRouterModels, allowAllOrganization)
|
|
expect(result).toBeUndefined()
|
|
})
|
|
|
|
it("excludes model-specific organization errors", () => {
|
|
const config: ProviderSettings = {
|
|
apiProvider: "openrouter",
|
|
openRouterApiKey: "valid-key",
|
|
openRouterModelId: "another-valid-model", // Not allowed by restrictive org
|
|
}
|
|
|
|
const result = validateApiConfigurationExcludingModelErrors(
|
|
config,
|
|
mockRouterModels,
|
|
restrictiveOrganization,
|
|
)
|
|
expect(result).toBeUndefined() // Should exclude model-specific org errors
|
|
})
|
|
})
|
|
})
|
|
|
|
describe("validateBedrockArn", () => {
|
|
describe("always returns isValid: true (no strict format validation)", () => {
|
|
it("accepts standard AWS Bedrock ARNs", () => {
|
|
const result = validateBedrockArn(
|
|
"arn:aws:bedrock:us-west-2:123456789012:inference-profile/us.anthropic.claude-3-5-sonnet-v2",
|
|
)
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.arnRegion).toBe("us-west-2")
|
|
expect(result.errorMessage).toBeUndefined()
|
|
})
|
|
|
|
it("accepts AWS GovCloud ARNs", () => {
|
|
const result = validateBedrockArn(
|
|
"arn:aws-us-gov:bedrock:us-gov-west-1:123456789012:inference-profile/model",
|
|
)
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.arnRegion).toBe("us-gov-west-1")
|
|
expect(result.errorMessage).toBeUndefined()
|
|
})
|
|
|
|
it("accepts AWS China ARNs", () => {
|
|
const result = validateBedrockArn("arn:aws-cn:bedrock:cn-north-1:123456789012:inference-profile/model")
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.arnRegion).toBe("cn-north-1")
|
|
expect(result.errorMessage).toBeUndefined()
|
|
})
|
|
|
|
it("accepts SageMaker ARNs", () => {
|
|
const result = validateBedrockArn("arn:aws:sagemaker:us-east-1:123456789012:endpoint/my-endpoint")
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.arnRegion).toBe("us-east-1")
|
|
expect(result.errorMessage).toBeUndefined()
|
|
})
|
|
|
|
it("accepts non-standard ARN formats without validation errors", () => {
|
|
// Users are advanced - trust their input
|
|
const result = validateBedrockArn("arn:custom:service:region:account:resource")
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.arnRegion).toBe("region")
|
|
expect(result.errorMessage).toBeUndefined()
|
|
})
|
|
|
|
it("accepts completely custom ARN strings", () => {
|
|
// Even unusual formats should be accepted
|
|
const result = validateBedrockArn("some-custom-arn-format")
|
|
expect(result.isValid).toBe(true)
|
|
// May not be able to extract region from non-standard format
|
|
expect(result.errorMessage).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe("region mismatch warnings", () => {
|
|
it("shows warning when ARN region differs from provided region", () => {
|
|
const result = validateBedrockArn(
|
|
"arn:aws:bedrock:us-west-2:123456789012:inference-profile/model",
|
|
"us-east-1",
|
|
)
|
|
expect(result.isValid).toBe(true) // Still valid, just a warning
|
|
expect(result.arnRegion).toBe("us-west-2")
|
|
expect(result.errorMessage).toBeDefined()
|
|
expect(result.errorMessage).toContain("us-west-2")
|
|
})
|
|
|
|
it("shows no warning when ARN region matches provided region", () => {
|
|
const result = validateBedrockArn(
|
|
"arn:aws:bedrock:us-west-2:123456789012:inference-profile/model",
|
|
"us-west-2",
|
|
)
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.arnRegion).toBe("us-west-2")
|
|
expect(result.errorMessage).toBeUndefined()
|
|
})
|
|
|
|
it("shows no warning when no region is provided to check against", () => {
|
|
const result = validateBedrockArn("arn:aws:bedrock:us-west-2:123456789012:inference-profile/model")
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.arnRegion).toBe("us-west-2")
|
|
expect(result.errorMessage).toBeUndefined()
|
|
})
|
|
|
|
it("shows no warning when region cannot be extracted from ARN", () => {
|
|
const result = validateBedrockArn("non-arn-format", "us-east-1")
|
|
expect(result.isValid).toBe(true)
|
|
expect(result.arnRegion).toBeUndefined()
|
|
expect(result.errorMessage).toBeUndefined()
|
|
})
|
|
})
|
|
})
|