318 lines
11 KiB
Go
318 lines
11 KiB
Go
//go:build unit
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestAccountTestService_KiroUsesKiroUpstreamInsteadOfAnthropic(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
ctx, _ := newTestContext()
|
|
|
|
account := &Account{
|
|
ID: 1,
|
|
Name: "kiro-test",
|
|
Platform: PlatformKiro,
|
|
Type: AccountTypeOAuth,
|
|
Concurrency: 1,
|
|
Credentials: map[string]any{
|
|
"access_token": "kiro-access-token",
|
|
"profile_arn": "arn:aws:codewhisperer:us-east-1:123456789012:profile/TESTSOCIAL",
|
|
},
|
|
}
|
|
repo := &mockAccountRepoForGemini{accountsByID: map[int64]*Account{1: account}}
|
|
upstream := &queuedHTTPUpstream{
|
|
responses: []*http.Response{
|
|
newJSONResponse(http.StatusUnauthorized, `{"type":"error","error":{"type":"authentication_error","message":"Invalid bearer token"}}`),
|
|
},
|
|
}
|
|
svc := &AccountTestService{
|
|
accountRepo: repo,
|
|
kiroTokenProvider: NewKiroTokenProvider(nil, nil, nil),
|
|
httpUpstream: upstream,
|
|
tlsFPProfileService: &TLSFingerprintProfileService{},
|
|
}
|
|
|
|
err := svc.TestAccountConnection(ctx, account.ID, "gpt-4o", "", AccountTestModeDefault)
|
|
require.Error(t, err)
|
|
require.Len(t, upstream.requests, 1)
|
|
|
|
req := upstream.requests[0]
|
|
require.Equal(t, "q.us-east-1.amazonaws.com", req.URL.Host)
|
|
require.Equal(t, "/generateAssistantResponse", req.URL.Path)
|
|
require.Equal(t, "Bearer kiro-access-token", req.Header.Get("Authorization"))
|
|
require.Equal(t, "vibe", req.Header.Get("x-amzn-kiro-agent-mode"))
|
|
require.Empty(t, req.Header.Get("anthropic-version"))
|
|
require.NotContains(t, req.URL.Host, "api.anthropic.com")
|
|
}
|
|
|
|
func TestAccountTestService_Kiro429DoesNotFallbackToCodeWhispererEndpoint(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
ctx, _ := newTestContext()
|
|
|
|
account := &Account{
|
|
ID: 2,
|
|
Name: "kiro-fallback",
|
|
Platform: PlatformKiro,
|
|
Type: AccountTypeOAuth,
|
|
Concurrency: 1,
|
|
Credentials: map[string]any{
|
|
"access_token": "kiro-access-token",
|
|
"api_region": "us-west-2",
|
|
"region": "us-west-2",
|
|
"profile_arn": "arn:aws:codewhisperer:us-west-2:123456789012:profile/TESTFALLBACK",
|
|
},
|
|
}
|
|
repo := &mockAccountRepoForGemini{accountsByID: map[int64]*Account{2: account}}
|
|
upstream := &queuedHTTPUpstream{
|
|
responses: []*http.Response{
|
|
newJSONResponse(http.StatusTooManyRequests, `{"message":"slow down"}`),
|
|
},
|
|
}
|
|
svc := &AccountTestService{
|
|
accountRepo: repo,
|
|
kiroTokenProvider: NewKiroTokenProvider(nil, nil, nil),
|
|
httpUpstream: upstream,
|
|
tlsFPProfileService: &TLSFingerprintProfileService{},
|
|
}
|
|
|
|
err := svc.TestAccountConnection(ctx, account.ID, "claude-sonnet-4-6", "", AccountTestModeDefault)
|
|
require.Error(t, err)
|
|
require.Len(t, upstream.requests, 1)
|
|
|
|
require.Equal(t, "q.us-west-2.amazonaws.com", upstream.requests[0].URL.Host)
|
|
require.Empty(t, upstream.requests[0].Header.Get("X-Amz-Target"))
|
|
require.Contains(t, err.Error(), "API returned 429")
|
|
}
|
|
|
|
func TestAccountTestService_KiroIDCWithoutProfileArnOmitsProfileArnAndUsesDefaultRuntimeRegion(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
ctx, _ := newTestContext()
|
|
|
|
account := &Account{
|
|
ID: 5,
|
|
Name: "kiro-idc-default-region",
|
|
Platform: PlatformKiro,
|
|
Type: AccountTypeOAuth,
|
|
Concurrency: 1,
|
|
Credentials: map[string]any{
|
|
"access_token": "kiro-access-token",
|
|
"auth_method": "idc",
|
|
"provider": "AWS",
|
|
"region": "ap-northeast-2",
|
|
"start_url": "https://d-example.awsapps.com/start",
|
|
},
|
|
}
|
|
repo := &mockAccountRepoForGemini{accountsByID: map[int64]*Account{5: account}}
|
|
upstream := &queuedHTTPUpstream{
|
|
responses: []*http.Response{
|
|
newJSONResponse(http.StatusUnauthorized, `{"type":"error","error":{"message":"Invalid bearer token"}}`),
|
|
},
|
|
}
|
|
svc := &AccountTestService{
|
|
accountRepo: repo,
|
|
kiroTokenProvider: NewKiroTokenProvider(nil, nil, nil),
|
|
httpUpstream: upstream,
|
|
tlsFPProfileService: &TLSFingerprintProfileService{},
|
|
}
|
|
|
|
err := svc.TestAccountConnection(ctx, account.ID, "claude-sonnet-4-6", "", AccountTestModeDefault)
|
|
require.Error(t, err)
|
|
require.Len(t, upstream.requests, 1)
|
|
require.Equal(t, "q.us-east-1.amazonaws.com", upstream.requests[0].URL.Host)
|
|
body, readErr := io.ReadAll(upstream.requests[0].Body)
|
|
require.NoError(t, readErr)
|
|
require.NotContains(t, string(body), `"profileArn":`)
|
|
}
|
|
|
|
func TestAccountTestService_KiroInvalidModelErrorPassthrough(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
ctx, _ := newTestContext()
|
|
|
|
account := &Account{
|
|
ID: 6,
|
|
Name: "kiro-invalid-model",
|
|
Platform: PlatformKiro,
|
|
Type: AccountTypeOAuth,
|
|
Concurrency: 1,
|
|
Credentials: map[string]any{
|
|
"access_token": "kiro-access-token",
|
|
"profile_arn": "arn:aws:codewhisperer:us-west-2:123456789012:profile/TESTINVALIDMODEL",
|
|
},
|
|
}
|
|
repo := &mockAccountRepoForGemini{accountsByID: map[int64]*Account{6: account}}
|
|
upstream := &queuedHTTPUpstream{
|
|
responses: []*http.Response{
|
|
newJSONResponse(http.StatusBadRequest, `{"message":"Invalid model ID. Please select a different model to continue.","reason":"INVALID_MODEL_ID"}`),
|
|
},
|
|
}
|
|
svc := &AccountTestService{
|
|
accountRepo: repo,
|
|
kiroTokenProvider: NewKiroTokenProvider(nil, nil, nil),
|
|
httpUpstream: upstream,
|
|
tlsFPProfileService: &TLSFingerprintProfileService{},
|
|
}
|
|
|
|
err := svc.TestAccountConnection(ctx, account.ID, "claude-opus-4-6", "", AccountTestModeDefault)
|
|
require.Error(t, err)
|
|
require.Equal(t, `API returned 400: {"message":"Invalid model ID. Please select a different model to continue.","reason":"INVALID_MODEL_ID"}`, err.Error())
|
|
}
|
|
|
|
func TestAccountTestService_KiroInvalidModelDoesNotRefreshProfileArnOrRetry(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
ctx, _ := newTestContext()
|
|
|
|
account := &Account{
|
|
ID: 7,
|
|
Name: "kiro-invalid-model-refresh",
|
|
Platform: PlatformKiro,
|
|
Type: AccountTypeOAuth,
|
|
Concurrency: 1,
|
|
Credentials: map[string]any{
|
|
"access_token": "kiro-access-token",
|
|
"profile_arn": "arn:aws:codewhisperer:us-east-1:123456789012:profile/STALE",
|
|
},
|
|
}
|
|
repo := &mockAccountRepoForGemini{accountsByID: map[int64]*Account{7: account}}
|
|
upstream := &queuedHTTPUpstream{
|
|
responses: []*http.Response{
|
|
newJSONResponse(http.StatusBadRequest, `{"message":"Invalid model ID. Please select a different model to continue.","reason":"INVALID_MODEL_ID"}`),
|
|
},
|
|
}
|
|
svc := &AccountTestService{
|
|
accountRepo: repo,
|
|
kiroTokenProvider: NewKiroTokenProvider(nil, nil, nil),
|
|
httpUpstream: upstream,
|
|
tlsFPProfileService: &TLSFingerprintProfileService{},
|
|
}
|
|
|
|
err := svc.TestAccountConnection(ctx, account.ID, "claude-opus-4-6", "", AccountTestModeDefault)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "API returned 400")
|
|
require.Len(t, upstream.requests, 1)
|
|
|
|
firstBody, readErr := io.ReadAll(upstream.requests[0].Body)
|
|
require.NoError(t, readErr)
|
|
require.Contains(t, string(firstBody), `"profileArn":"arn:aws:codewhisperer:us-east-1:123456789012:profile/STALE"`)
|
|
require.Equal(t, "arn:aws:codewhisperer:us-east-1:123456789012:profile/STALE", account.GetCredential("profile_arn"))
|
|
}
|
|
|
|
func TestAccountTestService_KiroPreferredEndpointIsIgnored(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
ctx, _ := newTestContext()
|
|
|
|
account := &Account{
|
|
ID: 6,
|
|
Name: "kiro-preferred-endpoint",
|
|
Platform: PlatformKiro,
|
|
Type: AccountTypeOAuth,
|
|
Concurrency: 1,
|
|
Credentials: map[string]any{
|
|
"access_token": "kiro-access-token",
|
|
"api_region": "us-west-2",
|
|
"profile_arn": "arn:aws:codewhisperer:us-west-2:123456789012:profile/PREFERRED",
|
|
"preferred_endpoint": "codewhisperer",
|
|
},
|
|
}
|
|
repo := &mockAccountRepoForGemini{accountsByID: map[int64]*Account{6: account}}
|
|
upstream := &queuedHTTPUpstream{
|
|
responses: []*http.Response{
|
|
newJSONResponse(http.StatusUnauthorized, `{"type":"error","error":{"message":"Invalid bearer token"}}`),
|
|
},
|
|
}
|
|
svc := &AccountTestService{
|
|
accountRepo: repo,
|
|
kiroTokenProvider: NewKiroTokenProvider(nil, nil, nil),
|
|
httpUpstream: upstream,
|
|
tlsFPProfileService: &TLSFingerprintProfileService{},
|
|
}
|
|
|
|
err := svc.TestAccountConnection(ctx, account.ID, "claude-sonnet-4-6", "", AccountTestModeDefault)
|
|
require.Error(t, err)
|
|
require.Len(t, upstream.requests, 1)
|
|
require.Equal(t, "q.us-west-2.amazonaws.com", upstream.requests[0].URL.Host)
|
|
require.Empty(t, upstream.requests[0].Header.Get("X-Amz-Target"))
|
|
}
|
|
|
|
func TestBuildKiroPayloadForAccount_KiroBuilderIDWithoutProfileArnOmitsProfileArn(t *testing.T) {
|
|
account := &Account{
|
|
ID: 3,
|
|
Name: "kiro-builder-id",
|
|
Platform: PlatformKiro,
|
|
Type: AccountTypeOAuth,
|
|
Credentials: map[string]any{
|
|
"auth_method": "idc",
|
|
"provider": "BuilderId",
|
|
"region": "us-east-1",
|
|
"client_id": "builder-client-id",
|
|
},
|
|
}
|
|
|
|
testPayload, err := createTestPayload("claude-sonnet-4-6")
|
|
require.NoError(t, err)
|
|
payloadBytes, err := json.Marshal(testPayload)
|
|
require.NoError(t, err)
|
|
|
|
kiroPayload, err := buildKiroPayloadForAccount(context.Background(), account, payloadBytes, "claude-sonnet-4-6", "kiro-access-token", "claude-sonnet-4-6", nil)
|
|
require.NoError(t, err)
|
|
require.NotContains(t, string(kiroPayload), `"profileArn":`)
|
|
}
|
|
|
|
func TestBuildKiroPayloadForAccount_KiroBuilderIDUsesCredentialProfileArn(t *testing.T) {
|
|
account := &Account{
|
|
ID: 33,
|
|
Name: "kiro-builder-id-cached",
|
|
Platform: PlatformKiro,
|
|
Type: AccountTypeOAuth,
|
|
Credentials: map[string]any{
|
|
"auth_method": "builder-id",
|
|
"provider": "BuilderId",
|
|
"region": "us-east-1",
|
|
"client_id": "builder-client-id",
|
|
"profile_arn": "arn:aws:codewhisperer:us-east-1:123456789012:profile/CACHED",
|
|
},
|
|
}
|
|
|
|
testPayload, err := createTestPayload("claude-sonnet-4-6")
|
|
require.NoError(t, err)
|
|
payloadBytes, err := json.Marshal(testPayload)
|
|
require.NoError(t, err)
|
|
|
|
kiroPayload, err := buildKiroPayloadForAccount(context.Background(), account, payloadBytes, "claude-sonnet-4-6", "kiro-access-token", "claude-sonnet-4-6", nil)
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(kiroPayload), `"profileArn":"arn:aws:codewhisperer:us-east-1:123456789012:profile/CACHED"`)
|
|
}
|
|
|
|
func TestBuildKiroPayloadForAccount_KiroEnterpriseIDCOmitsMissingProfileArn(t *testing.T) {
|
|
account := &Account{
|
|
ID: 4,
|
|
Name: "kiro-enterprise-idc",
|
|
Platform: PlatformKiro,
|
|
Type: AccountTypeOAuth,
|
|
Credentials: map[string]any{
|
|
"auth_method": "idc",
|
|
"provider": "AWS",
|
|
"region": "us-east-1",
|
|
"client_id": "enterprise-client-id",
|
|
"start_url": "https://d-example.awsapps.com/start",
|
|
},
|
|
}
|
|
|
|
testPayload, err := createTestPayload("claude-sonnet-4-6")
|
|
require.NoError(t, err)
|
|
payloadBytes, err := json.Marshal(testPayload)
|
|
require.NoError(t, err)
|
|
|
|
kiroPayload, err := buildKiroPayloadForAccount(context.Background(), account, payloadBytes, "claude-sonnet-4-6", "kiro-access-token", "claude-sonnet-4-6", nil)
|
|
require.NoError(t, err)
|
|
require.NotContains(t, string(kiroPayload), `"profileArn":`)
|
|
}
|