package llmcost import ( "testing" "github.com/stretchr/testify/assert" ) func TestNormalizeModelIDProviderNames(t *testing.T) { tests := []struct { input string expected string }{ {"openai", "openai"}, {"azure", "azure"}, {"azure_ai", "azure"}, {"anthropic", "anthropic"}, {"google", "google"}, {"vertex_ai", "vertex_ai"}, {"gemini", "google"}, {"amazon", "amazon"}, {"aws", "amazon"}, {"bedrock", "bedrock"}, {"bedrock_converse", "bedrock"}, {"amazon-bedrock", "bedrock"}, {"meta", "meta"}, {"facebook", "meta"}, {"deepseek", "deepseek"}, {"mistral", "mistral"}, {"mistralai", "mistral"}, {"cohere", "cohere"}, {"xai", "xai"}, {"x-ai", "xai"}, {"minimax", "minimax"}, {"nano-gpt", "nanogpt"}, {"nano_gpt", "nanogpt"}, {"nanogpt", "nanogpt"}, {"vertex_ai-language-models", "vertex_ai"}, {"vertex_ai-text-models", "vertex_ai"}, {"vertex_ai-chat-models", "vertex_ai"}, {"vertex_ai_something", "vertex_ai"}, {"unknown_provider", "unknown_provider"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { provider, _ := NormalizeModelID(tt.input, "some-model") assert.Equal(t, tt.expected, provider) }) } } func TestNormalizeModelIDCaseAndWhitespace(t *testing.T) { t.Run("lowercases provider", func(t *testing.T) { provider, _ := NormalizeModelID("OpenAI", "model") assert.Equal(t, "openai", provider) }) t.Run("lowercases model ID", func(t *testing.T) { _, modelID := NormalizeModelID("openai", "GPT-4o") assert.Equal(t, "gpt-4o", modelID) }) t.Run("trims whitespace", func(t *testing.T) { provider, modelID := NormalizeModelID(" openai ", " gpt-4 ") assert.Equal(t, "openai", provider) assert.Equal(t, "gpt-4", modelID) }) } func TestNormalizeProviderCaseAndWhitespace(t *testing.T) { tests := []struct { name string input string expected string }{ {"lowercases", "Azure", "azure"}, {"trims whitespace", " azure ", "azure"}, {"lowercases and trims", " OpenAI ", "openai"}, {"mixed case unknown", " MyProvider ", "myprovider"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.Equal(t, tt.expected, NormalizeProvider(tt.input)) }) } } func TestNormalizeModelIDVersionSuffix(t *testing.T) { tests := []struct { name string input string expected string }{ {"strips 8-digit date suffix", "claude-3-5-sonnet-20241022", "claude-3-5-sonnet"}, {"strips another 8-digit date suffix", "gpt-4-turbo-20240409", "gpt-4-turbo"}, {"strips hyphenated date suffix", "gpt-5-nano-2025-08-07", "gpt-5-nano"}, {"strips hyphenated date suffix 2", "gpt-4o-2024-08-06", "gpt-4o"}, {"no suffix unchanged", "gpt-4o", "gpt-4o"}, {"suffix in middle unchanged", "model-20241022-beta", "model-20241022-beta"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, modelID := NormalizeModelID("openai", tt.input) assert.Equal(t, tt.expected, modelID) }) } } func TestNormalizeModelIDBedrockVersionSuffix(t *testing.T) { tests := []struct { name string input string expected string }{ {"strips -v1:0", "anthropic.claude-3-5-sonnet-v1:0", "anthropic.claude-3-5-sonnet"}, {"strips -v2:0", "anthropic.claude-3-5-sonnet-v2:0", "anthropic.claude-3-5-sonnet"}, {"strips date then version", "anthropic.claude-3-5-sonnet-20240620-v1:0", "anthropic.claude-3-5-sonnet"}, {"strips from nova model", "amazon.nova-pro-v1:0", "amazon.nova-pro"}, {"strips from llama model", "meta.llama3-1-70b-instruct-v1:0", "meta.llama3-1-70b-instruct"}, {"no version suffix unchanged", "anthropic.claude-sonnet-4-6", "anthropic.claude-sonnet-4-6"}, {"non-bedrock model unchanged", "gpt-4o", "gpt-4o"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, modelID := NormalizeModelID("bedrock", tt.input) assert.Equal(t, tt.expected, modelID) }) } } func TestNormalizeModelIDBedrockRegionPrefix(t *testing.T) { tests := []struct { name string input string expected string }{ {"strips eu prefix", "eu.anthropic.claude-sonnet-4-6", "anthropic.claude-sonnet-4-6"}, {"strips us prefix", "us.anthropic.claude-opus-4", "anthropic.claude-opus-4"}, {"strips ap prefix", "ap.meta.llama3-1-70b-instruct", "meta.llama3-1-70b-instruct"}, {"no prefix unchanged", "anthropic.claude-sonnet-4-6", "anthropic.claude-sonnet-4-6"}, {"strips prefix and version", "us.anthropic.claude-3-5-sonnet-20241022-v1:0", "anthropic.claude-3-5-sonnet"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, modelID := NormalizeModelID("bedrock", tt.input) assert.Equal(t, tt.expected, modelID) }) } } func TestNormalizeModelIDDotVersion(t *testing.T) { tests := []struct { name string provider string input string expected string }{ {"claude 3.5 dot to hyphen", "anthropic", "claude-3.5-sonnet", "claude-3-5-sonnet"}, {"claude 3.5 haiku", "anthropic", "claude-3.5-haiku", "claude-3-5-haiku"}, {"gemini 2.0", "google", "gemini-2.0-flash", "gemini-2-0-flash"}, {"already hyphens unchanged", "anthropic", "claude-3-5-sonnet", "claude-3-5-sonnet"}, {"namespace dot preserved", "bedrock", "anthropic.claude-3-5-sonnet", "anthropic.claude-3-5-sonnet"}, {"no version dots unchanged", "openai", "gpt-4o", "gpt-4o"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, modelID := NormalizeModelID(tt.provider, tt.input) assert.Equal(t, tt.expected, modelID) }) } } func TestNormalizeModelIDAliases(t *testing.T) { tests := []struct { name string input string expected string }{ {"deepseek-chat to deepseek-v3", "deepseek-chat", "deepseek-v3"}, {"deepseek-reasoner to deepseek-r1", "deepseek-reasoner", "deepseek-r1"}, {"deepseek-v3 unchanged", "deepseek-v3", "deepseek-v3"}, {"deepseek-r1 unchanged", "deepseek-r1", "deepseek-r1"}, {"non-alias unchanged", "gpt-4o", "gpt-4o"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, modelID := NormalizeModelID("deepseek", tt.input) assert.Equal(t, tt.expected, modelID) }) } }