package service import ( "testing" "github.com/stretchr/testify/require" ) func TestBuildSelectedSet(t *testing.T) { tests := []struct { name string ids []string wantNil bool wantSize int }{ { name: "nil input returns nil (backward compatible: create all)", ids: nil, wantNil: true, }, { name: "empty slice returns empty map (create none)", ids: []string{}, wantNil: false, wantSize: 0, }, { name: "single ID", ids: []string{"abc-123"}, wantNil: false, wantSize: 1, }, { name: "multiple IDs", ids: []string{"a", "b", "c"}, wantNil: false, wantSize: 3, }, { name: "duplicate IDs are deduplicated", ids: []string{"a", "a", "b"}, wantNil: false, wantSize: 2, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := buildSelectedSet(tt.ids) if tt.wantNil { if got != nil { t.Errorf("buildSelectedSet(%v) = %v, want nil", tt.ids, got) } return } if got == nil { t.Fatalf("buildSelectedSet(%v) = nil, want non-nil map", tt.ids) } if len(got) != tt.wantSize { t.Errorf("buildSelectedSet(%v) has %d entries, want %d", tt.ids, len(got), tt.wantSize) } // Verify all unique IDs are present for _, id := range tt.ids { if _, ok := got[id]; !ok { t.Errorf("buildSelectedSet(%v) missing key %q", tt.ids, id) } } }) } } func TestShouldCreateAccount(t *testing.T) { tests := []struct { name string crsID string selectedSet map[string]struct{} want bool }{ { name: "nil set allows all (backward compatible)", crsID: "any-id", selectedSet: nil, want: true, }, { name: "empty set blocks all", crsID: "any-id", selectedSet: map[string]struct{}{}, want: false, }, { name: "ID in set is allowed", crsID: "abc-123", selectedSet: map[string]struct{}{"abc-123": {}, "def-456": {}}, want: true, }, { name: "ID not in set is blocked", crsID: "xyz-789", selectedSet: map[string]struct{}{"abc-123": {}, "def-456": {}}, want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := shouldCreateAccount(tt.crsID, tt.selectedSet) if got != tt.want { t.Errorf("shouldCreateAccount(%q, %v) = %v, want %v", tt.crsID, tt.selectedSet, got, tt.want) } }) } } func TestReconcileCRSUpstreamBillingProbeExtra(t *testing.T) { remote := map[string]any{ "crs_account_id": "remote-1", UpstreamBillingProbeEnabledExtraKey: true, UpstreamBillingRateSyncEnabledExtraKey: true, UpstreamBillingProbeExtraKey: map[string]any{"status": "remote"}, } t.Run("create drops remote managed fields", func(t *testing.T) { extra := mergeMap(nil, remote) reconcileCRSUpstreamBillingProbeExtra(nil, PlatformOpenAI, AccountTypeAPIKey, map[string]any{"api_key": "new"}, extra) require.NotContains(t, extra, UpstreamBillingProbeEnabledExtraKey) require.NotContains(t, extra, UpstreamBillingRateSyncEnabledExtraKey) require.NotContains(t, extra, UpstreamBillingProbeExtraKey) }) existing := &Account{ Platform: PlatformOpenAI, Type: AccountTypeAPIKey, Credentials: map[string]any{"api_key": "local", "base_url": "http://127.0.0.1:8080"}, Extra: map[string]any{ UpstreamBillingProbeEnabledExtraKey: false, UpstreamBillingRateSyncEnabledExtraKey: false, UpstreamBillingProbeExtraKey: map[string]any{"status": "local"}, }, } t.Run("same identity keeps local state", func(t *testing.T) { extra := mergeMap(existing.Extra, remote) reconcileCRSUpstreamBillingProbeExtra(existing, existing.Platform, existing.Type, mergeMap(existing.Credentials, nil), extra) require.Equal(t, false, extra[UpstreamBillingProbeEnabledExtraKey]) require.Equal(t, false, extra[UpstreamBillingRateSyncEnabledExtraKey]) require.Equal(t, map[string]any{"status": "local"}, extra[UpstreamBillingProbeExtraKey]) }) t.Run("same identity preserves enabled rate sync", func(t *testing.T) { enabled := *existing enabled.Extra = mergeMap(existing.Extra, map[string]any{ UpstreamBillingProbeEnabledExtraKey: true, UpstreamBillingRateSyncEnabledExtraKey: true, }) extra := mergeMap(enabled.Extra, remote) reconcileCRSUpstreamBillingProbeExtra(&enabled, enabled.Platform, enabled.Type, mergeMap(enabled.Credentials, nil), extra) require.Equal(t, true, extra[UpstreamBillingProbeEnabledExtraKey]) require.Equal(t, true, extra[UpstreamBillingRateSyncEnabledExtraKey]) }) t.Run("identity change keeps enabled and clears snapshot", func(t *testing.T) { extra := mergeMap(existing.Extra, remote) reconcileCRSUpstreamBillingProbeExtra(existing, PlatformOpenAI, AccountTypeAPIKey, map[string]any{"api_key": "changed"}, extra) require.Equal(t, false, extra[UpstreamBillingProbeEnabledExtraKey]) require.Equal(t, false, extra[UpstreamBillingRateSyncEnabledExtraKey]) require.NotContains(t, extra, UpstreamBillingProbeExtraKey) }) // API-key 平台间切换:探测资格保留(放宽后不再限 OpenAI),开关沿用本地值, // 但平台属于探测身份,快照必须作废。 for _, target := range []struct { name string platform string }{ {name: "anthropic api key", platform: PlatformAnthropic}, {name: "gemini api key", platform: PlatformGemini}, } { t.Run(target.name+" keeps enabled and clears snapshot", func(t *testing.T) { extra := mergeMap(existing.Extra, remote) reconcileCRSUpstreamBillingProbeExtra(existing, target.platform, AccountTypeAPIKey, existing.Credentials, extra) require.Equal(t, false, extra[UpstreamBillingProbeEnabledExtraKey]) require.Equal(t, false, extra[UpstreamBillingRateSyncEnabledExtraKey]) require.NotContains(t, extra, UpstreamBillingProbeExtraKey) }) } for _, target := range []struct { name string platform string typeName string }{ {name: "anthropic oauth", platform: PlatformAnthropic, typeName: AccountTypeOAuth}, {name: "openai oauth", platform: PlatformOpenAI, typeName: AccountTypeOAuth}, {name: "gemini oauth", platform: PlatformGemini, typeName: AccountTypeOAuth}, } { t.Run(target.name+" removes inapplicable state", func(t *testing.T) { extra := mergeMap(existing.Extra, remote) reconcileCRSUpstreamBillingProbeExtra(existing, target.platform, target.typeName, existing.Credentials, extra) require.NotContains(t, extra, UpstreamBillingProbeEnabledExtraKey) require.NotContains(t, extra, UpstreamBillingRateSyncEnabledExtraKey) require.NotContains(t, extra, UpstreamBillingProbeExtraKey) }) } }