Files
sub2api/backend/internal/service/gateway_service_bedrock_beta_test.go
2026-03-13 17:00:16 +08:00

268 lines
7.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"context"
"encoding/json"
"testing"
"github.com/Wei-Shaw/sub2api/internal/config"
)
type betaPolicySettingRepoStub struct {
values map[string]string
}
func (s *betaPolicySettingRepoStub) Get(ctx context.Context, key string) (*Setting, error) {
panic("unexpected Get call")
}
func (s *betaPolicySettingRepoStub) GetValue(ctx context.Context, key string) (string, error) {
if v, ok := s.values[key]; ok {
return v, nil
}
return "", ErrSettingNotFound
}
func (s *betaPolicySettingRepoStub) Set(ctx context.Context, key, value string) error {
panic("unexpected Set call")
}
func (s *betaPolicySettingRepoStub) GetMultiple(ctx context.Context, keys []string) (map[string]string, error) {
panic("unexpected GetMultiple call")
}
func (s *betaPolicySettingRepoStub) SetMultiple(ctx context.Context, settings map[string]string) error {
panic("unexpected SetMultiple call")
}
func (s *betaPolicySettingRepoStub) GetAll(ctx context.Context) (map[string]string, error) {
panic("unexpected GetAll call")
}
func (s *betaPolicySettingRepoStub) Delete(ctx context.Context, key string) error {
panic("unexpected Delete call")
}
func TestResolveBedrockBetaTokensForRequest_BlocksOnOriginalAnthropicToken(t *testing.T) {
settings := &BetaPolicySettings{
Rules: []BetaPolicyRule{
{
BetaToken: "advanced-tool-use-2025-11-20",
Action: BetaPolicyActionBlock,
Scope: BetaPolicyScopeAll,
ErrorMessage: "advanced tool use is blocked",
},
},
}
raw, err := json.Marshal(settings)
if err != nil {
t.Fatalf("marshal settings: %v", err)
}
svc := &GatewayService{
settingService: NewSettingService(
&betaPolicySettingRepoStub{values: map[string]string{
SettingKeyBetaPolicySettings: string(raw),
}},
&config.Config{},
),
}
account := &Account{Platform: PlatformAnthropic, Type: AccountTypeBedrock}
_, err = svc.resolveBedrockBetaTokensForRequest(
context.Background(),
account,
"advanced-tool-use-2025-11-20",
[]byte(`{"messages":[{"role":"user","content":"hi"}]}`),
"us.anthropic.claude-opus-4-6-v1",
)
if err == nil {
t.Fatal("expected raw advanced-tool-use token to be blocked before Bedrock transform")
}
if err.Error() != "advanced tool use is blocked" {
t.Fatalf("unexpected error: %v", err)
}
}
func TestResolveBedrockBetaTokensForRequest_FiltersAfterBedrockTransform(t *testing.T) {
settings := &BetaPolicySettings{
Rules: []BetaPolicyRule{
{
BetaToken: "tool-search-tool-2025-10-19",
Action: BetaPolicyActionFilter,
Scope: BetaPolicyScopeAll,
},
},
}
raw, err := json.Marshal(settings)
if err != nil {
t.Fatalf("marshal settings: %v", err)
}
svc := &GatewayService{
settingService: NewSettingService(
&betaPolicySettingRepoStub{values: map[string]string{
SettingKeyBetaPolicySettings: string(raw),
}},
&config.Config{},
),
}
account := &Account{Platform: PlatformAnthropic, Type: AccountTypeBedrock}
betaTokens, err := svc.resolveBedrockBetaTokensForRequest(
context.Background(),
account,
"advanced-tool-use-2025-11-20",
[]byte(`{"messages":[{"role":"user","content":"hi"}]}`),
"us.anthropic.claude-opus-4-6-v1",
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for _, token := range betaTokens {
if token == "tool-search-tool-2025-10-19" {
t.Fatalf("expected transformed Bedrock token to be filtered")
}
}
}
// TestResolveBedrockBetaTokensForRequest_BlocksBodyAutoInjectedThinking 验证:
// 管理员 block 了 interleaved-thinking客户端不在 header 中带该 token
// 但请求体包含 thinking 字段 → 自动注入后应被 block。
func TestResolveBedrockBetaTokensForRequest_BlocksBodyAutoInjectedThinking(t *testing.T) {
settings := &BetaPolicySettings{
Rules: []BetaPolicyRule{
{
BetaToken: "interleaved-thinking-2025-05-14",
Action: BetaPolicyActionBlock,
Scope: BetaPolicyScopeAll,
ErrorMessage: "thinking is blocked",
},
},
}
raw, err := json.Marshal(settings)
if err != nil {
t.Fatalf("marshal settings: %v", err)
}
svc := &GatewayService{
settingService: NewSettingService(
&betaPolicySettingRepoStub{values: map[string]string{
SettingKeyBetaPolicySettings: string(raw),
}},
&config.Config{},
),
}
account := &Account{Platform: PlatformAnthropic, Type: AccountTypeBedrock}
// header 中不带 beta token但 body 中有 thinking 字段
_, err = svc.resolveBedrockBetaTokensForRequest(
context.Background(),
account,
"", // 空 header
[]byte(`{"thinking":{"type":"enabled","budget_tokens":10000},"messages":[{"role":"user","content":"hi"}]}`),
"us.anthropic.claude-opus-4-6-v1",
)
if err == nil {
t.Fatal("expected body-injected interleaved-thinking to be blocked")
}
if err.Error() != "thinking is blocked" {
t.Fatalf("unexpected error: %v", err)
}
}
// TestResolveBedrockBetaTokensForRequest_BlocksBodyAutoInjectedToolSearch 验证:
// 管理员 block 了 tool-search-tool客户端不在 header 中带 beta token
// 但请求体包含 tool search 工具 → 自动注入后应被 block。
func TestResolveBedrockBetaTokensForRequest_BlocksBodyAutoInjectedToolSearch(t *testing.T) {
settings := &BetaPolicySettings{
Rules: []BetaPolicyRule{
{
BetaToken: "tool-search-tool-2025-10-19",
Action: BetaPolicyActionBlock,
Scope: BetaPolicyScopeAll,
ErrorMessage: "tool search is blocked",
},
},
}
raw, err := json.Marshal(settings)
if err != nil {
t.Fatalf("marshal settings: %v", err)
}
svc := &GatewayService{
settingService: NewSettingService(
&betaPolicySettingRepoStub{values: map[string]string{
SettingKeyBetaPolicySettings: string(raw),
}},
&config.Config{},
),
}
account := &Account{Platform: PlatformAnthropic, Type: AccountTypeBedrock}
// header 中不带 beta token但 body 中有 tool_search_tool 工具
_, err = svc.resolveBedrockBetaTokensForRequest(
context.Background(),
account,
"",
[]byte(`{"tools":[{"type":"tool_search_tool_regex_20251119","name":"search"}],"messages":[{"role":"user","content":"hi"}]}`),
"us.anthropic.claude-sonnet-4-6",
)
if err == nil {
t.Fatal("expected body-injected tool-search-tool to be blocked")
}
if err.Error() != "tool search is blocked" {
t.Fatalf("unexpected error: %v", err)
}
}
// TestResolveBedrockBetaTokensForRequest_PassesWhenNoBlockRuleMatches 验证:
// body 自动注入的 token 如果没有对应的 block 规则,应正常通过。
func TestResolveBedrockBetaTokensForRequest_PassesWhenNoBlockRuleMatches(t *testing.T) {
settings := &BetaPolicySettings{
Rules: []BetaPolicyRule{
{
BetaToken: "computer-use-2025-11-24",
Action: BetaPolicyActionBlock,
Scope: BetaPolicyScopeAll,
ErrorMessage: "computer use is blocked",
},
},
}
raw, err := json.Marshal(settings)
if err != nil {
t.Fatalf("marshal settings: %v", err)
}
svc := &GatewayService{
settingService: NewSettingService(
&betaPolicySettingRepoStub{values: map[string]string{
SettingKeyBetaPolicySettings: string(raw),
}},
&config.Config{},
),
}
account := &Account{Platform: PlatformAnthropic, Type: AccountTypeBedrock}
// body 中有 thinking会注入 interleaved-thinking但 block 规则只针对 computer-use
tokens, err := svc.resolveBedrockBetaTokensForRequest(
context.Background(),
account,
"",
[]byte(`{"thinking":{"type":"enabled","budget_tokens":10000},"messages":[{"role":"user","content":"hi"}]}`),
"us.anthropic.claude-opus-4-6-v1",
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
found := false
for _, token := range tokens {
if token == "interleaved-thinking-2025-05-14" {
found = true
}
}
if !found {
t.Fatal("expected interleaved-thinking token to be present")
}
}