Merge pull request #2052 from praxstack/feat/bedrock-bearer-token-auth

feat(bedrock): dynamic region fetching and AWS_PROFILE conflict fix
This commit is contained in:
Kayvan Sylvan 2026-03-09 08:49:51 -07:00 committed by GitHub
commit 9057f97739
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 252 additions and 15 deletions

View file

@ -0,0 +1,5 @@
### PR [#2052](https://github.com/danielmiessler/Fabric/pull/2052) by [praxstack](https://github.com/praxstack) and [ksylvan](https://github.com/ksylvan): feat(bedrock): dynamic region fetching and AWS_PROFILE conflict fix
- Added dynamic Bedrock region fetching from `botocore`'s `endpoints.json`, expanding support to 40+ regions instead of a hardcoded list of 6, with a static fallback on error.
- Fixed `AWS_PROFILE` environment variable conflict that caused `'failed to get shared config profile'` errors for users who had `AWS_PROFILE` set for other tools (e.g., Terraform, AWS CLI) while using explicit access keys or static credentials.
- Improved empty auth choice handling to gracefully skip the selection instead of throwing an error.

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "leerer Antwortinhalt",
"bedrock_failed_list_foundation_models": "Foundation-Modelle konnten nicht aufgelistet werden: %w",
"bedrock_failed_list_inference_profiles": "Inferenz-Profile konnten nicht aufgelistet werden: %w",
"bedrock_fetch_regions_bad_status": "botocore-Endpunkte haben einen unerwarteten Status zurückgegeben",
"bedrock_fetch_regions_failed": "Bedrock-Regionen konnten nicht von botocore abgerufen werden",
"bedrock_fetch_regions_parse_failed": "botocore endpoints.json konnte nicht analysiert werden",
"bedrock_invalid_aws_region": "ungültige AWS-Region: %s",
"bedrock_listmodels_fallback": "Bedrock ListModels API fehlgeschlagen, verwende statische Fallback-Liste",
"bedrock_panic_sendstream": "Panic in SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "empty response content",
"bedrock_failed_list_foundation_models": "failed to list foundation models: %w",
"bedrock_failed_list_inference_profiles": "failed to list inference profiles: %w",
"bedrock_fetch_regions_bad_status": "botocore endpoints returned unexpected status",
"bedrock_fetch_regions_failed": "failed to fetch Bedrock regions from botocore",
"bedrock_fetch_regions_parse_failed": "failed to parse botocore endpoints.json",
"bedrock_invalid_aws_region": "invalid AWS region: %s",
"bedrock_listmodels_fallback": "Bedrock ListModels API failed, using static fallback",
"bedrock_panic_sendstream": "panic in SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "contenido de respuesta vacío",
"bedrock_failed_list_foundation_models": "error al listar modelos base: %w",
"bedrock_failed_list_inference_profiles": "error al listar perfiles de inferencia: %w",
"bedrock_fetch_regions_bad_status": "los endpoints de botocore devolvieron un estado inesperado",
"bedrock_fetch_regions_failed": "no se pudieron obtener las regiones de Bedrock desde botocore",
"bedrock_fetch_regions_parse_failed": "no se pudo analizar el archivo endpoints.json de botocore",
"bedrock_invalid_aws_region": "región de AWS inválida: %s",
"bedrock_listmodels_fallback": "la API ListModels de Bedrock falló, usando lista estática de respaldo",
"bedrock_panic_sendstream": "pánico en SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "محتوای پاسخ خالی",
"bedrock_failed_list_foundation_models": "فهرست کردن مدل‌های پایه ناموفق بود: %w",
"bedrock_failed_list_inference_profiles": "فهرست کردن پروفایل‌های استنتاج ناموفق بود: %w",
"bedrock_fetch_regions_bad_status": "نقاط پایانی botocore وضعیت غیرمنتظره‌ای برگرداندند",
"bedrock_fetch_regions_failed": "دریافت مناطق Bedrock از botocore ناموفق بود",
"bedrock_fetch_regions_parse_failed": "تجزیه فایل endpoints.json بوتوکور ناموفق بود",
"bedrock_invalid_aws_region": "منطقه AWS نامعتبر: %s",
"bedrock_listmodels_fallback": "API ListModels Bedrock ناموفق بود، استفاده از لیست پشتیبان ثابت",
"bedrock_panic_sendstream": "پنیک در SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "contenu de réponse vide",
"bedrock_failed_list_foundation_models": "échec de la liste des modèles de fondation : %w",
"bedrock_failed_list_inference_profiles": "échec de la liste des profils d'inférence : %w",
"bedrock_fetch_regions_bad_status": "les endpoints botocore ont renvoyé un statut inattendu",
"bedrock_fetch_regions_failed": "échec de la récupération des régions Bedrock depuis botocore",
"bedrock_fetch_regions_parse_failed": "échec de l analyse du fichier endpoints.json de botocore",
"bedrock_invalid_aws_region": "région AWS invalide : %s",
"bedrock_listmodels_fallback": "API ListModels Bedrock échouée, utilisation de la liste statique de secours",
"bedrock_panic_sendstream": "panique dans SendStream : %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "contenuto della risposta vuoto",
"bedrock_failed_list_foundation_models": "impossibile elencare i modelli di fondazione: %w",
"bedrock_failed_list_inference_profiles": "impossibile elencare i profili di inferenza: %w",
"bedrock_fetch_regions_bad_status": "gli endpoint botocore hanno restituito uno stato imprevisto",
"bedrock_fetch_regions_failed": "impossibile recuperare le regioni Bedrock da botocore",
"bedrock_fetch_regions_parse_failed": "impossibile analizzare il file endpoints.json di botocore",
"bedrock_invalid_aws_region": "regione AWS non valida: %s",
"bedrock_listmodels_fallback": "API ListModels Bedrock fallita, utilizzo della lista statica di fallback",
"bedrock_panic_sendstream": "panic in SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "空のレスポンスコンテンツ",
"bedrock_failed_list_foundation_models": "基盤モデルの一覧表示に失敗しました: %w",
"bedrock_failed_list_inference_profiles": "推論プロファイルの一覧表示に失敗しました: %w",
"bedrock_fetch_regions_bad_status": "botocoreエンドポイントが予期しないステータスを返しました",
"bedrock_fetch_regions_failed": "botocoreからBedrocリージョンの取得に失敗しました",
"bedrock_fetch_regions_parse_failed": "botocore endpoints.jsonの解析に失敗しました",
"bedrock_invalid_aws_region": "無効なAWSリージョン: %s",
"bedrock_listmodels_fallback": "Bedrock ListModels APIが失敗しました、静的フォールバックリストを使用",
"bedrock_panic_sendstream": "SendStreamでパニックが発生しました: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "pusta zawartość odpowiedzi",
"bedrock_failed_list_foundation_models": "nie udało się wylistować modeli podstawowych: %w",
"bedrock_failed_list_inference_profiles": "nie udało się wylistować profili wnioskowania: %w",
"bedrock_fetch_regions_bad_status": "punkty końcowe botocore zwróciły nieoczekiwany status",
"bedrock_fetch_regions_failed": "nie udało się pobrać regionów Bedrock z botocore",
"bedrock_fetch_regions_parse_failed": "nie udało się przeanalizować pliku endpoints.json botocore",
"bedrock_invalid_aws_region": "nieprawidłowy region AWS: %s",
"bedrock_listmodels_fallback": "API ListModels Bedrock nie powiodło się, używam statycznej listy zapasowej",
"bedrock_panic_sendstream": "panika w SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "conteudo de resposta vazio",
"bedrock_failed_list_foundation_models": "falha ao listar modelos de fundacao: %w",
"bedrock_failed_list_inference_profiles": "falha ao listar perfis de inferencia: %w",
"bedrock_fetch_regions_bad_status": "endpoints do botocore retornaram status inesperado",
"bedrock_fetch_regions_failed": "falha ao buscar regiões Bedrock do botocore",
"bedrock_fetch_regions_parse_failed": "falha ao analisar o arquivo endpoints.json do botocore",
"bedrock_invalid_aws_region": "regiao AWS invalida: %s",
"bedrock_listmodels_fallback": "API ListModels do Bedrock falhou, usando lista estática de fallback",
"bedrock_panic_sendstream": "panico no SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "conteudo de resposta vazio",
"bedrock_failed_list_foundation_models": "falha ao listar modelos de fundacao: %w",
"bedrock_failed_list_inference_profiles": "falha ao listar perfis de inferencia: %w",
"bedrock_fetch_regions_bad_status": "endpoints do botocore retornaram estado inesperado",
"bedrock_fetch_regions_failed": "falha ao obter regiões Bedrock do botocore",
"bedrock_fetch_regions_parse_failed": "falha ao analisar o ficheiro endpoints.json do botocore",
"bedrock_invalid_aws_region": "regiao AWS invalida: %s",
"bedrock_listmodels_fallback": "API ListModels do Bedrock falhou, usando lista estática de recurso",
"bedrock_panic_sendstream": "panico no SendStream: %v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -60,7 +60,11 @@
"bedrock_empty_response_content": "空响应内容",
"bedrock_failed_list_foundation_models": "无法列出基础模型:%w",
"bedrock_failed_list_inference_profiles": "无法列出推理配置文件:%w",
"bedrock_fetch_regions_bad_status": "botocore 端点返回了意外状态",
"bedrock_fetch_regions_failed": "从 botocore 获取 Bedrock 区域失败",
"bedrock_fetch_regions_parse_failed": "解析 botocore endpoints.json 失败",
"bedrock_invalid_aws_region": "无效的 AWS 区域:%s",
"bedrock_listmodels_fallback": "Bedrock ListModels API 失败,使用静态回退列表",
"bedrock_panic_sendstream": "SendStream 中出现恐慌:%v",
"bedrock_setup_auth_option_accesskey": " [2] AWS Access Key + Secret Key",
"bedrock_setup_auth_option_apikey": " [1] Bedrock API Key / ABSK token (recommended — same as Claude Code)",

View file

@ -10,9 +10,13 @@ package bedrock
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"sort"
"strings"
"time"
"github.com/danielmiessler/fabric/internal/domain"
"github.com/danielmiessler/fabric/internal/i18n"
@ -111,8 +115,8 @@ var setupModelChoices = []string{
"ap.anthropic.claude-opus-4-6-v1",
}
// Common AWS regions for Bedrock
var awsRegions = []string{
// fallbackRegions is used only when the dynamic fetch from botocore fails (e.g., no network).
var fallbackRegions = []string{
"us-east-1",
"us-west-2",
"eu-west-1",
@ -121,6 +125,61 @@ var awsRegions = []string{
"ap-northeast-1",
}
// botocoreEndpointsURL is the public (no-auth) source of truth for which AWS
// regions support Bedrock, maintained by the AWS SDK team.
// This is a var (not const) to allow test injection of a mock HTTP server URL.
var botocoreEndpointsURL = "https://raw.githubusercontent.com/boto/botocore/develop/botocore/data/endpoints.json"
// fetchBedrockRegions fetches the list of AWS regions where Bedrock is available
// from the botocore endpoints.json file (public, no authentication required).
// Falls back to the static fallbackRegions list on any error.
func fetchBedrockRegions() []string {
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get(botocoreEndpointsURL)
if err != nil {
debuglog.Log(i18n.T("bedrock_fetch_regions_failed")+": %v\n", err)
return fallbackRegions
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
debuglog.Log(i18n.T("bedrock_fetch_regions_bad_status")+": %d\n", resp.StatusCode)
return fallbackRegions
}
var data struct {
Partitions []struct {
Services map[string]struct {
Endpoints map[string]any `json:"endpoints"`
} `json:"services"`
} `json:"partitions"`
}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
debuglog.Log(i18n.T("bedrock_fetch_regions_parse_failed")+": %v\n", err)
return fallbackRegions
}
var regions []string
for _, partition := range data.Partitions {
if svc, ok := partition.Services["bedrock"]; ok {
for region := range svc.Endpoints {
// Skip FIPS and special endpoints (e.g., "bedrock-us-east-1")
if !strings.HasPrefix(region, "bedrock-") && !strings.Contains(region, "fips") {
regions = append(regions, region)
}
}
}
}
if len(regions) == 0 {
return fallbackRegions
}
sort.Strings(regions)
return regions
}
// maskSecret redacts a secret value for display, showing only the first 4 and last 4 chars.
func maskSecret(s string) string {
if len(s) <= 12 {
@ -170,6 +229,11 @@ func (c *BedrockClient) Setup() (err error) {
return
}
// Empty input means skip (user pressed enter without typing)
if authChoice.Value == "" {
return nil
}
switch authChoice.Value {
case "1":
// Mask existing API key value before displaying the prompt
@ -211,10 +275,11 @@ func (c *BedrockClient) Setup() (err error) {
return fmt.Errorf(i18n.T("bedrock_setup_invalid_auth_selection"), authChoice.Value)
}
// Region selection
// Region selection — fetched dynamically from botocore (public, no auth required)
regions := fetchBedrockRegions()
fmt.Println()
fmt.Println(i18n.T("bedrock_setup_choose_region"))
for i, r := range awsRegions {
for i, r := range regions {
fmt.Printf(" [%d] %s\n", i+1, r)
}
fmt.Println(i18n.T("bedrock_setup_region_option_custom"))
@ -226,8 +291,8 @@ func (c *BedrockClient) Setup() (err error) {
}
regionNum := 0
if _, scanErr := fmt.Sscanf(regionChoice.Value, "%d", &regionNum); scanErr == nil && regionNum >= 1 && regionNum <= len(awsRegions) {
c.bedrockRegion.Value = awsRegions[regionNum-1]
if _, scanErr := fmt.Sscanf(regionChoice.Value, "%d", &regionNum); scanErr == nil && regionNum >= 1 && regionNum <= len(regions) {
c.bedrockRegion.Value = regions[regionNum-1]
} else if regionNum == 0 || regionChoice.Value == "0" {
customRegion := plugins.NewSetupQuestion(i18n.T("bedrock_setup_region_custom_prompt"))
if err = customRegion.Ask("Bedrock"); err != nil {
@ -326,6 +391,10 @@ func (c *BedrockClient) configure() error {
// AWS SDK's SigV4 auth middleware. AnonymousCredentials causes the SDK to fall
// through to its bearer token auth path, which panics without a token provider.
// Our bearerTokenTransport overrides the Authorization header with the real token.
// When using explicit credentials (bearer token or static keys), bypass the
// AWS shared config/credentials files to prevent AWS_PROFILE env var from
// causing "failed to get shared config profile" errors. This is thread-safe
// (no process-global env mutation) and only affects this config load.
if c.bedrockAPIKey.Value != "" {
configOpts = append(configOpts,
config.WithCredentialsProvider(
@ -337,16 +406,22 @@ func (c *BedrockClient) configure() error {
wrapped: http.DefaultTransport,
},
}),
config.WithSharedConfigFiles([]string{}),
config.WithSharedCredentialsFiles([]string{}),
)
} else if c.bedrockAccessKey.Value != "" && c.bedrockSecretKey.Value != "" {
// Priority 2: Explicit access key + secret key (static credentials)
configOpts = append(configOpts, config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider(
c.bedrockAccessKey.Value,
c.bedrockSecretKey.Value,
"", // session token (empty for long-term credentials)
configOpts = append(configOpts,
config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider(
c.bedrockAccessKey.Value,
c.bedrockSecretKey.Value,
"", // session token (empty for long-term credentials)
),
),
))
config.WithSharedConfigFiles([]string{}),
config.WithSharedCredentialsFiles([]string{}),
)
}
// Priority 3: No explicit credentials → AWS SDK uses the default credential chain
// (AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY env vars, ~/.aws/credentials, IAM roles, etc.)
@ -373,7 +448,7 @@ func (c *BedrockClient) ListModels() ([]string, error) {
if err != nil && c.bedrockAPIKey.Value != "" {
// Bearer token auth may lack ListFoundationModels permissions;
// return common models as fallback
debuglog.Log("Bedrock ListModels API failed (using static fallback): %v\n", err)
debuglog.Log(i18n.T("bedrock_listmodels_fallback")+": %v\n", err)
return defaultBedrockModels, nil
}
return models, err

View file

@ -2,11 +2,14 @@ package bedrock
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/danielmiessler/fabric/internal/chat"
"github.com/danielmiessler/fabric/internal/domain"
"github.com/danielmiessler/fabric/internal/i18n"
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime/types"
"github.com/stretchr/testify/assert"
@ -70,6 +73,7 @@ func TestConfigure_InvalidRegion_ReturnsError(t *testing.T) {
}
func TestConfigure_ValidRegion_BearerToken(t *testing.T) {
t.Setenv("AWS_PROFILE", "")
client := NewClient()
client.bedrockRegion.Value = "us-east-1"
client.bedrockAPIKey.Value = "test-absk-token"
@ -83,6 +87,7 @@ func TestConfigure_ValidRegion_BearerToken(t *testing.T) {
}
func TestConfigure_ValidRegion_StaticCredentials(t *testing.T) {
t.Setenv("AWS_PROFILE", "")
client := NewClient()
client.bedrockRegion.Value = "us-west-2"
client.bedrockAccessKey.Value = "AKIAIOSFODNN7EXAMPLE"
@ -96,6 +101,7 @@ func TestConfigure_ValidRegion_StaticCredentials(t *testing.T) {
}
func TestConfigure_ValidRegion_DefaultChain(t *testing.T) {
t.Setenv("AWS_PROFILE", "")
client := NewClient()
client.bedrockRegion.Value = "eu-west-1"
// No API key, no access key — should fall back to default credential chain
@ -108,6 +114,7 @@ func TestConfigure_ValidRegion_DefaultChain(t *testing.T) {
}
func TestConfigure_BearerTokenPriority(t *testing.T) {
t.Setenv("AWS_PROFILE", "")
// If both API key and access key are provided, API key (bearer) should win
client := NewClient()
client.bedrockRegion.Value = "us-east-1"
@ -222,7 +229,7 @@ func TestSendStream_NilClient_ReturnsError(t *testing.T) {
err := client.SendStream(nil, opts, ch)
assert.Error(t, err, "SendStream should return error when client is nil")
assert.Contains(t, err.Error(), "not initialized")
assert.Contains(t, err.Error(), i18n.T("bedrock_client_not_initialized"))
}
func TestSend_NilClient_ReturnsError(t *testing.T) {
@ -232,7 +239,7 @@ func TestSend_NilClient_ReturnsError(t *testing.T) {
opts := &domain.ChatOptions{Model: "test-model"}
_, err := client.Send(context.Background(), nil, opts)
assert.Error(t, err, "Send should return error when client is nil")
assert.Contains(t, err.Error(), "not initialized")
assert.Contains(t, err.Error(), i18n.T("bedrock_client_not_initialized"))
}
func TestMaskSecret(t *testing.T) {
@ -323,6 +330,112 @@ func TestToMessages_Empty(t *testing.T) {
assert.Empty(t, result)
}
// --- fetchBedrockRegions mock HTTP tests ---
// withMockEndpointsURL temporarily overrides the botocore endpoints URL for testing.
// NOTE: Not safe with t.Parallel() — tests using this helper must run sequentially.
func withMockEndpointsURL(url string, fn func()) {
orig := botocoreEndpointsURL
botocoreEndpointsURL = url
defer func() { botocoreEndpointsURL = orig }()
fn()
}
func TestFetchBedrockRegions_ValidJSON(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{"partitions":[{"services":{"bedrock":{"endpoints":{"us-east-1":{},"eu-west-1":{},"ap-southeast-1":{},"bedrock-us-east-1":{"hostname":"x"}}}}}]}`)
}))
defer server.Close()
withMockEndpointsURL(server.URL, func() {
regions := fetchBedrockRegions()
assert.Contains(t, regions, "us-east-1")
assert.Contains(t, regions, "eu-west-1")
assert.Contains(t, regions, "ap-southeast-1")
// bedrock- prefixed should be filtered
for _, r := range regions {
assert.False(t, len(r) > 8 && r[:8] == "bedrock-", "should filter bedrock- prefix: %s", r)
}
})
}
func TestFetchBedrockRegions_HTTPError_ReturnsFallback(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
}))
defer server.Close()
withMockEndpointsURL(server.URL, func() {
regions := fetchBedrockRegions()
assert.Equal(t, fallbackRegions, regions)
})
}
func TestFetchBedrockRegions_InvalidJSON_ReturnsFallback(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{not valid json`)
}))
defer server.Close()
withMockEndpointsURL(server.URL, func() {
regions := fetchBedrockRegions()
assert.Equal(t, fallbackRegions, regions)
})
}
func TestFetchBedrockRegions_NoBedrock_ReturnsFallback(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{"partitions":[{"services":{"s3":{"endpoints":{"us-east-1":{}}}}}]}`)
}))
defer server.Close()
withMockEndpointsURL(server.URL, func() {
regions := fetchBedrockRegions()
assert.Equal(t, fallbackRegions, regions)
})
}
func TestFetchBedrockRegions_Unreachable_ReturnsFallback(t *testing.T) {
withMockEndpointsURL("http://127.0.0.1:1", func() {
regions := fetchBedrockRegions()
assert.Equal(t, fallbackRegions, regions)
})
}
func TestFetchBedrockRegions_EmptyEndpoints_ReturnsFallback(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{"partitions":[{"services":{"bedrock":{"endpoints":{}}}}]}`)
}))
defer server.Close()
withMockEndpointsURL(server.URL, func() {
regions := fetchBedrockRegions()
assert.Equal(t, fallbackRegions, regions)
})
}
func TestFetchBedrockRegions_ResultsSorted(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{"partitions":[{"services":{"bedrock":{"endpoints":{"us-west-2":{},"ap-northeast-1":{},"eu-central-1":{}}}}}]}`)
}))
defer server.Close()
withMockEndpointsURL(server.URL, func() {
regions := fetchBedrockRegions()
require.Len(t, regions, 3)
assert.Equal(t, "ap-northeast-1", regions[0])
assert.Equal(t, "eu-central-1", regions[1])
assert.Equal(t, "us-west-2", regions[2])
})
}
func TestFallbackRegions_NotEmpty(t *testing.T) {
assert.NotEmpty(t, fallbackRegions)
for _, r := range fallbackRegions {
assert.True(t, isValidAWSRegion(r), "fallback region %q should be valid", r)
}
}
// roundTripFunc is a helper to create http.RoundTripper from a function
type roundTripFunc func(req *http.Request) (*http.Response, error)