package admin import ( "context" "encoding/json" "net/http" "net/http/httptest" "slices" "testing" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) type availableModelsAdminService struct { *stubAdminService account service.Account } func (s *availableModelsAdminService) GetAccount(_ context.Context, id int64) (*service.Account, error) { if s.account.ID == id { acc := s.account return &acc, nil } return s.stubAdminService.GetAccount(context.Background(), id) } func setupAvailableModelsRouter(adminSvc service.AdminService) *gin.Engine { gin.SetMode(gin.TestMode) router := gin.New() handler := NewAccountHandler(adminSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) router.GET("/api/v1/admin/accounts/:id/models", handler.GetAvailableModels) return router } func TestAccountHandlerGetAvailableModels_OpenAIOAuthUsesExplicitModelMapping(t *testing.T) { svc := &availableModelsAdminService{ stubAdminService: newStubAdminService(), account: service.Account{ ID: 42, Name: "openai-oauth", Platform: service.PlatformOpenAI, Type: service.AccountTypeOAuth, Status: service.StatusActive, Credentials: map[string]any{ "model_mapping": map[string]any{ "gpt-5": "gpt-5.1", }, }, }, } router := setupAvailableModelsRouter(svc) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts/42/models", nil) router.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code) var resp struct { Data []struct { ID string `json:"id"` } `json:"data"` } require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) require.Len(t, resp.Data, 1) require.Equal(t, "gpt-5", resp.Data[0].ID) } func TestAccountHandlerGetAvailableModels_OpenAIOAuthPassthroughFallsBackToDefaults(t *testing.T) { svc := &availableModelsAdminService{ stubAdminService: newStubAdminService(), account: service.Account{ ID: 43, Name: "openai-oauth-passthrough", Platform: service.PlatformOpenAI, Type: service.AccountTypeOAuth, Status: service.StatusActive, Credentials: map[string]any{ "model_mapping": map[string]any{ "gpt-5": "gpt-5.1", }, }, Extra: map[string]any{ "openai_passthrough": true, }, }, } router := setupAvailableModelsRouter(svc) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts/43/models", nil) router.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code) var resp struct { Data []struct { ID string `json:"id"` } `json:"data"` } require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) require.NotEmpty(t, resp.Data) require.NotEqual(t, "gpt-5", resp.Data[0].ID) } func TestAccountHandlerGetAvailableModels_KiroOAuthFallsBackToDefaults(t *testing.T) { svc := &availableModelsAdminService{ stubAdminService: newStubAdminService(), account: service.Account{ ID: 44, Name: "kiro-oauth", Platform: service.PlatformKiro, Type: service.AccountTypeOAuth, Status: service.StatusActive, }, } router := setupAvailableModelsRouter(svc) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts/44/models", nil) router.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code) var resp struct { Data []struct { ID string `json:"id"` } `json:"data"` } require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) require.NotEmpty(t, resp.Data) ids := make([]string, 0, len(resp.Data)) for _, model := range resp.Data { ids = append(ids, model.ID) } require.True(t, slices.Contains(ids, "claude-opus-4-6")) require.False(t, slices.Contains(ids, "claude-opus-4-7")) require.False(t, slices.Contains(ids, "kiro-claude-opus-4-7")) } func TestAccountHandlerGetAvailableModels_KiroOAuthUsesExplicitModelMapping(t *testing.T) { svc := &availableModelsAdminService{ stubAdminService: newStubAdminService(), account: service.Account{ ID: 47, Name: "kiro-oauth-mapped", Platform: service.PlatformKiro, Type: service.AccountTypeOAuth, Status: service.StatusActive, Credentials: map[string]any{ "model_mapping": map[string]any{ "claude-sonnet-4-6": "claude-sonnet-4.6", "custom-model": "custom-upstream-model", }, }, }, } router := setupAvailableModelsRouter(svc) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts/47/models", nil) router.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code) var resp struct { Data []struct { ID string `json:"id"` } `json:"data"` } require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) require.Len(t, resp.Data, 2) ids := make([]string, 0, len(resp.Data)) for _, model := range resp.Data { ids = append(ids, model.ID) } require.True(t, slices.Contains(ids, "claude-sonnet-4-6")) require.True(t, slices.Contains(ids, "custom-model")) require.False(t, slices.Contains(ids, "claude-opus-4-7")) } func TestAccountHandlerGetAvailableModels_KiroAPIKeyUsesExplicitModelMapping(t *testing.T) { svc := &availableModelsAdminService{ stubAdminService: newStubAdminService(), account: service.Account{ ID: 45, Name: "kiro-apikey", Platform: service.PlatformKiro, Type: service.AccountTypeAPIKey, Status: service.StatusActive, Credentials: map[string]any{ "model_mapping": map[string]any{ "claude-sonnet-4-6": "claude-sonnet-4.6", "custom-model": "custom-upstream-model", }, }, }, } router := setupAvailableModelsRouter(svc) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts/45/models", nil) router.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code) var resp struct { Data []struct { ID string `json:"id"` } `json:"data"` } require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) require.Len(t, resp.Data, 2) ids := make([]string, 0, len(resp.Data)) for _, model := range resp.Data { ids = append(ids, model.ID) } require.True(t, slices.Contains(ids, "claude-sonnet-4-6")) require.True(t, slices.Contains(ids, "custom-model")) } func TestAccountHandlerGetAvailableModels_KiroAPIKeyWithoutMappingFallsBackToDefaults(t *testing.T) { svc := &availableModelsAdminService{ stubAdminService: newStubAdminService(), account: service.Account{ ID: 46, Name: "kiro-apikey-defaults", Platform: service.PlatformKiro, Type: service.AccountTypeAPIKey, Status: service.StatusActive, }, } router := setupAvailableModelsRouter(svc) rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts/46/models", nil) router.ServeHTTP(rec, req) require.Equal(t, http.StatusOK, rec.Code) var resp struct { Data []struct { ID string `json:"id"` } `json:"data"` } require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) require.NotEmpty(t, resp.Data) ids := make([]string, 0, len(resp.Data)) for _, model := range resp.Data { ids = append(ids, model.ID) } require.True(t, slices.Contains(ids, "claude-opus-4-6")) require.False(t, slices.Contains(ids, "claude-opus-4-7")) require.False(t, slices.Contains(ids, "kiro-claude-opus-4-7")) }