| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360 |
- //go:build unit
- package service
- import (
- "context"
- "testing"
- "github.com/stretchr/testify/require"
- )
- func TestOpenAISelectAccountForModelWithExclusions_ChannelMappedRestrictionRejectsEarly(t *testing.T) {
- t.Parallel()
- channelSvc := newTestChannelService(makeStandardRepo(Channel{
- ID: 1,
- Status: StatusActive,
- GroupIDs: []int64{10},
- RestrictModels: true,
- BillingModelSource: BillingModelSourceChannelMapped,
- ModelPricing: []ChannelModelPricing{
- {Platform: PlatformOpenAI, Models: []string{"gpt-4o"}},
- },
- ModelMapping: map[string]map[string]string{
- PlatformOpenAI: {"gpt-4.1": "o3-mini"},
- },
- }, map[int64]string{10: PlatformOpenAI}))
- svc := &OpenAIGatewayService{
- accountRepo: stubOpenAIAccountRepo{accounts: []Account{
- {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true},
- }},
- channelService: channelSvc,
- }
- groupID := int64(10)
- _, err := svc.SelectAccountForModelWithExclusions(context.Background(), &groupID, "", "gpt-4.1", nil)
- require.ErrorIs(t, err, ErrNoAvailableAccounts)
- require.Contains(t, err.Error(), "channel pricing restriction")
- }
- func TestOpenAISelectAccountForModelWithExclusions_UpstreamRestrictionSkipsDisallowedAccount(t *testing.T) {
- t.Parallel()
- channelSvc := newTestChannelService(makeStandardRepo(Channel{
- ID: 1,
- Status: StatusActive,
- GroupIDs: []int64{10},
- RestrictModels: true,
- BillingModelSource: BillingModelSourceUpstream,
- ModelPricing: []ChannelModelPricing{
- {Platform: PlatformOpenAI, Models: []string{"o3-mini"}},
- },
- }, map[int64]string{10: PlatformOpenAI}))
- svc := &OpenAIGatewayService{
- accountRepo: stubOpenAIAccountRepo{accounts: []Account{
- {
- ID: 1,
- Platform: PlatformOpenAI,
- Status: StatusActive,
- Schedulable: true,
- Priority: 10,
- Credentials: map[string]any{
- "model_mapping": map[string]any{"gpt-4.1": "gpt-4o"},
- },
- },
- {
- ID: 2,
- Platform: PlatformOpenAI,
- Status: StatusActive,
- Schedulable: true,
- Priority: 20,
- Credentials: map[string]any{
- "model_mapping": map[string]any{"gpt-4.1": "o3-mini"},
- },
- },
- }},
- channelService: channelSvc,
- }
- groupID := int64(10)
- account, err := svc.SelectAccountForModelWithExclusions(context.Background(), &groupID, "", "gpt-4.1", nil)
- require.NoError(t, err)
- require.NotNil(t, account)
- require.Equal(t, int64(2), account.ID)
- }
- func TestIsUpstreamModelRestrictedByChannel_CompactMappingMatchesForwardPath(t *testing.T) {
- t.Parallel()
- account := &Account{
- Platform: PlatformOpenAI,
- Credentials: map[string]any{
- "model_mapping": map[string]any{"gpt-5.4-channel": "gpt-5.4-account"},
- "compact_model_mapping": map[string]any{"gpt-5.4-account": "gpt-5.4-compact"},
- },
- }
- tests := []struct {
- name string
- allowedUpstreamModel string
- useCompactModelMapping bool
- }{
- {
- name: "legacy compact applies compact mapping after channel and account mapping",
- allowedUpstreamModel: "gpt-5.4-compact",
- useCompactModelMapping: true,
- },
- {
- name: "native v2 stops after channel and account mapping",
- allowedUpstreamModel: "gpt-5.4-account",
- useCompactModelMapping: false,
- },
- }
- for _, tt := range tests {
- tt := tt
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
- channelSvc := newTestChannelService(makeStandardRepo(Channel{
- ID: 1,
- Status: StatusActive,
- GroupIDs: []int64{10},
- RestrictModels: true,
- BillingModelSource: BillingModelSourceUpstream,
- ModelPricing: []ChannelModelPricing{
- {Platform: PlatformOpenAI, Models: []string{tt.allowedUpstreamModel}},
- },
- ModelMapping: map[string]map[string]string{
- PlatformOpenAI: {"gpt-5.4": "gpt-5.4-channel"},
- },
- }, map[int64]string{10: PlatformOpenAI}))
- svc := &OpenAIGatewayService{channelService: channelSvc}
- mapping := channelSvc.ResolveChannelMapping(context.Background(), 10, "gpt-5.4")
- require.True(t, mapping.Mapped)
- require.Equal(t, "gpt-5.4-channel", mapping.MappedModel)
- ctx := WithOpenAIForwardModel(
- context.Background(),
- mapping.MappedModel,
- tt.useCompactModelMapping,
- )
- require.False(t, svc.isUpstreamModelRestrictedByChannel(
- ctx, 10, account, "gpt-5.4", true,
- ))
- require.True(t, svc.isUpstreamModelRestrictedByChannel(
- context.Background(), 10, account, "gpt-5.4", true,
- ), "without the forward-model context the restriction check follows a different chain")
- })
- }
- }
- func TestIsUpstreamModelRestrictedByChannel_PassthroughMatchesForwardPath(t *testing.T) {
- t.Parallel()
- account := &Account{
- Platform: PlatformOpenAI,
- Type: AccountTypeAPIKey,
- Credentials: map[string]any{
- "model_mapping": map[string]any{"gpt-5.4-channel": "gpt-5.4-account"},
- "compact_model_mapping": map[string]any{
- "gpt-5.4-channel": "gpt-5.4-compact",
- },
- },
- Extra: map[string]any{"openai_passthrough": true},
- }
- tests := []struct {
- name string
- allowedUpstreamModel string
- useCompactModelMapping bool
- }{
- {
- name: "native v2 keeps channel-mapped model and ignores normal account mapping",
- allowedUpstreamModel: "gpt-5.4-channel",
- useCompactModelMapping: false,
- },
- {
- name: "legacy compact applies compact mapping to channel-mapped model",
- allowedUpstreamModel: "gpt-5.4-compact",
- useCompactModelMapping: true,
- },
- }
- for _, tt := range tests {
- tt := tt
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
- channelSvc := newTestChannelService(makeStandardRepo(Channel{
- ID: 1,
- Status: StatusActive,
- GroupIDs: []int64{10},
- RestrictModels: true,
- BillingModelSource: BillingModelSourceUpstream,
- ModelPricing: []ChannelModelPricing{
- {Platform: PlatformOpenAI, Models: []string{tt.allowedUpstreamModel}},
- },
- ModelMapping: map[string]map[string]string{
- PlatformOpenAI: {"gpt-5.4": "gpt-5.4-channel"},
- },
- }, map[int64]string{10: PlatformOpenAI}))
- svc := &OpenAIGatewayService{channelService: channelSvc}
- mapping := channelSvc.ResolveChannelMapping(context.Background(), 10, "gpt-5.4")
- require.True(t, mapping.Mapped)
- require.Equal(t, "gpt-5.4-channel", mapping.MappedModel)
- ctx := WithOpenAIForwardModel(
- context.Background(),
- mapping.MappedModel,
- tt.useCompactModelMapping,
- )
- require.False(t, svc.isUpstreamModelRestrictedByChannel(
- ctx, 10, account, "gpt-5.4", true,
- ))
- })
- }
- }
- func TestIsUpstreamModelRestrictedByChannel_PassthroughFlagWithRawChatFallbackMatchesForwardPath(t *testing.T) {
- t.Parallel()
- account := &Account{
- Platform: PlatformOpenAI,
- Type: AccountTypeAPIKey,
- Credentials: map[string]any{
- "model_mapping": map[string]any{"gpt-5.4-channel": "gpt-5.4-account"},
- "compact_model_mapping": map[string]any{
- "gpt-5.4-account": "gpt-5.4-compact",
- },
- },
- Extra: map[string]any{
- "openai_passthrough": true,
- "openai_responses_supported": false,
- },
- }
- for _, useCompactModelMapping := range []bool{false, true} {
- useCompactModelMapping := useCompactModelMapping
- name := "native v2"
- if useCompactModelMapping {
- name = "legacy compact"
- }
- t.Run(name, func(t *testing.T) {
- t.Parallel()
- channelSvc := newTestChannelService(makeStandardRepo(Channel{
- ID: 1,
- Status: StatusActive,
- GroupIDs: []int64{10},
- RestrictModels: true,
- BillingModelSource: BillingModelSourceUpstream,
- ModelPricing: []ChannelModelPricing{
- {Platform: PlatformOpenAI, Models: []string{"gpt-5.4-account"}},
- },
- ModelMapping: map[string]map[string]string{
- PlatformOpenAI: {"gpt-5.4": "gpt-5.4-channel"},
- },
- }, map[int64]string{10: PlatformOpenAI}))
- svc := &OpenAIGatewayService{channelService: channelSvc}
- ctx := WithOpenAIForwardModel(
- context.Background(),
- "gpt-5.4-channel",
- useCompactModelMapping,
- )
- require.False(t, svc.isUpstreamModelRestrictedByChannel(
- ctx, 10, account, "gpt-5.4", true,
- ))
- })
- }
- }
- func TestIsUpstreamModelRestrictedByChannel_ForwardModelContextMatchesNormalForwardPath(t *testing.T) {
- t.Parallel()
- account := &Account{
- Platform: PlatformOpenAI,
- Type: AccountTypeAPIKey,
- Credentials: map[string]any{
- "model_mapping": map[string]any{"gpt-5.4-channel": "gpt-5.4-account"},
- },
- Extra: map[string]any{
- "openai_passthrough": true,
- "openai_responses_supported": false,
- },
- }
- channelSvc := newTestChannelService(makeStandardRepo(Channel{
- ID: 1,
- Status: StatusActive,
- GroupIDs: []int64{10},
- RestrictModels: true,
- BillingModelSource: BillingModelSourceUpstream,
- ModelPricing: []ChannelModelPricing{
- {Platform: PlatformOpenAI, Models: []string{"gpt-5.4-account"}},
- },
- ModelMapping: map[string]map[string]string{
- PlatformOpenAI: {"gpt-5.4": "gpt-5.4-channel"},
- },
- }, map[int64]string{10: PlatformOpenAI}))
- svc := &OpenAIGatewayService{channelService: channelSvc}
- ctx := WithOpenAIForwardModel(context.Background(), "gpt-5.4-channel", false)
- require.False(t, svc.isUpstreamModelRestrictedByChannel(
- ctx, 10, account, "gpt-5.4", false,
- ))
- }
- func TestOpenAISelectAccountForModelWithExclusions_StickyRestrictedUpstreamFallsBack(t *testing.T) {
- t.Parallel()
- channelSvc := newTestChannelService(makeStandardRepo(Channel{
- ID: 1,
- Status: StatusActive,
- GroupIDs: []int64{10},
- RestrictModels: true,
- BillingModelSource: BillingModelSourceUpstream,
- ModelPricing: []ChannelModelPricing{
- {Platform: PlatformOpenAI, Models: []string{"o3-mini"}},
- },
- }, map[int64]string{10: PlatformOpenAI}))
- cache := &stubGatewayCache{
- sessionBindings: map[string]int64{"openai:sticky-session": 1},
- }
- svc := &OpenAIGatewayService{
- accountRepo: stubOpenAIAccountRepo{accounts: []Account{
- {
- ID: 1,
- Platform: PlatformOpenAI,
- Status: StatusActive,
- Schedulable: true,
- Priority: 10,
- Credentials: map[string]any{
- "model_mapping": map[string]any{"gpt-4.1": "gpt-4o"},
- },
- },
- {
- ID: 2,
- Platform: PlatformOpenAI,
- Status: StatusActive,
- Schedulable: true,
- Priority: 20,
- Credentials: map[string]any{
- "model_mapping": map[string]any{"gpt-4.1": "o3-mini"},
- },
- },
- }},
- channelService: channelSvc,
- cache: cache,
- }
- groupID := int64(10)
- account, err := svc.SelectAccountForModelWithExclusions(context.Background(), &groupID, "sticky-session", "gpt-4.1", nil)
- require.NoError(t, err)
- require.NotNil(t, account)
- require.Equal(t, int64(2), account.ID)
- require.Equal(t, 1, cache.deletedSessions["openai:sticky-session"])
- require.Equal(t, int64(2), cache.sessionBindings["openai:sticky-session"])
- }
|