mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
299 lines
9.3 KiB
Go
299 lines
9.3 KiB
Go
package admin
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type affinityCacheAdminService struct {
|
|
*stubAdminService
|
|
accounts map[int64]*service.Account
|
|
bulkResultIDs []int64
|
|
bulkFailedCount int
|
|
}
|
|
|
|
func (s *affinityCacheAdminService) GetAccount(_ context.Context, id int64) (*service.Account, error) {
|
|
if acc, ok := s.accounts[id]; ok {
|
|
cloned := *acc
|
|
return &cloned, nil
|
|
}
|
|
return s.stubAdminService.GetAccount(context.Background(), id)
|
|
}
|
|
|
|
func (s *affinityCacheAdminService) UpdateAccount(_ context.Context, id int64, input *service.UpdateAccountInput) (*service.Account, error) {
|
|
acc := s.accounts[id]
|
|
if acc == nil {
|
|
return s.stubAdminService.UpdateAccount(context.Background(), id, input)
|
|
}
|
|
if input.Extra != nil {
|
|
acc.Extra = input.Extra
|
|
}
|
|
if input.GroupIDs != nil {
|
|
acc.GroupIDs = *input.GroupIDs
|
|
}
|
|
cloned := *acc
|
|
return &cloned, nil
|
|
}
|
|
|
|
func (s *affinityCacheAdminService) BulkUpdateAccounts(_ context.Context, input *service.BulkUpdateAccountsInput) (*service.BulkUpdateAccountsResult, error) {
|
|
for _, accountID := range input.AccountIDs {
|
|
acc := s.accounts[accountID]
|
|
if acc == nil {
|
|
continue
|
|
}
|
|
if input.Extra != nil {
|
|
acc.Extra = input.Extra
|
|
}
|
|
if input.GroupIDs != nil {
|
|
acc.GroupIDs = *input.GroupIDs
|
|
}
|
|
}
|
|
successIDs := append([]int64(nil), input.AccountIDs...)
|
|
failed := 0
|
|
if len(s.bulkResultIDs) > 0 {
|
|
successIDs = append([]int64(nil), s.bulkResultIDs...)
|
|
failed = s.bulkFailedCount
|
|
}
|
|
return &service.BulkUpdateAccountsResult{
|
|
Success: len(successIDs),
|
|
Failed: failed,
|
|
SuccessIDs: successIDs,
|
|
Results: []service.BulkUpdateAccountResult{},
|
|
}, nil
|
|
}
|
|
|
|
type affinityCacheClearRecorder struct {
|
|
clearCalls []struct {
|
|
accountID int64
|
|
groupIDs []int64
|
|
}
|
|
}
|
|
|
|
func (r *affinityCacheClearRecorder) GetSessionAccountID(_ context.Context, _ int64, _ string) (int64, error) {
|
|
return 0, nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) SetSessionAccountID(_ context.Context, _ int64, _ string, _ int64, _ time.Duration) error {
|
|
return nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) RefreshSessionTTL(_ context.Context, _ int64, _ string, _ time.Duration) error {
|
|
return nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) DeleteSessionAccountID(_ context.Context, _ int64, _ string) error {
|
|
return nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) GetAffinityAccounts(_ context.Context, _ int64, _ int64, _ string, _ time.Duration) ([]int64, error) {
|
|
return nil, nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) UpdateAffinity(_ context.Context, _ int64, _ int64, _ string, _ int64, _ time.Duration) error {
|
|
return nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) GetAccountAffinityCountBatch(_ context.Context, _ int64, _ []int64, _ time.Duration) (map[int64]int64, error) {
|
|
return map[int64]int64{}, nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) GetAccountAffinityClientsBatch(_ context.Context, _ map[int64][]int64, _ time.Duration) (map[int64][]string, error) {
|
|
return map[int64][]string{}, nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) GetAccountAffinityClientsWithScores(_ context.Context, _ int64, _ []int64, _ time.Duration) ([]service.AffinityClient, error) {
|
|
return nil, nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) ClearAccountAffinity(_ context.Context, accountID int64, groupIDs []int64) error {
|
|
r.clearCalls = append(r.clearCalls, struct {
|
|
accountID int64
|
|
groupIDs []int64
|
|
}{
|
|
accountID: accountID,
|
|
groupIDs: append([]int64(nil), groupIDs...),
|
|
})
|
|
return nil
|
|
}
|
|
func (r *affinityCacheClearRecorder) GetAffinityMultiCount(_ context.Context, _ int64, _ int64, _ int64, _ time.Duration) (users, clients, perUser int64, err error) {
|
|
return 0, 0, 0, nil
|
|
}
|
|
|
|
func setupAffinityCacheRouter(adminSvc service.AdminService, gatewayCache service.GatewayCache) *gin.Engine {
|
|
gin.SetMode(gin.TestMode)
|
|
router := gin.New()
|
|
handler := NewAccountHandler(adminSvc, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, gatewayCache)
|
|
router.PUT("/api/v1/admin/accounts/:id", handler.Update)
|
|
router.POST("/api/v1/admin/accounts/bulk-update", handler.BulkUpdate)
|
|
return router
|
|
}
|
|
|
|
func TestAccountHandlerUpdate_DisablingAffinityClearsCache(t *testing.T) {
|
|
adminSvc := &affinityCacheAdminService{
|
|
stubAdminService: newStubAdminService(),
|
|
accounts: map[int64]*service.Account{
|
|
3: {
|
|
ID: 3,
|
|
Platform: service.PlatformAnthropic,
|
|
Type: service.AccountTypeOAuth,
|
|
Status: service.StatusActive,
|
|
GroupIDs: []int64{1, 2},
|
|
Extra: map[string]any{
|
|
"client_affinity_enabled": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
cache := &affinityCacheClearRecorder{}
|
|
router := setupAffinityCacheRouter(adminSvc, cache)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"extra": map[string]any{},
|
|
})
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPut, "/api/v1/admin/accounts/3", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
router.ServeHTTP(rec, req)
|
|
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Len(t, cache.clearCalls, 1)
|
|
require.Equal(t, int64(3), cache.clearCalls[0].accountID)
|
|
require.Equal(t, []int64{1, 2}, cache.clearCalls[0].groupIDs)
|
|
}
|
|
|
|
func TestAccountHandlerBulkUpdate_DisablingAffinityClearsCache(t *testing.T) {
|
|
adminSvc := &affinityCacheAdminService{
|
|
stubAdminService: newStubAdminService(),
|
|
accounts: map[int64]*service.Account{
|
|
1: {
|
|
ID: 1,
|
|
Platform: service.PlatformAnthropic,
|
|
Type: service.AccountTypeOAuth,
|
|
Status: service.StatusActive,
|
|
GroupIDs: []int64{11},
|
|
Extra: map[string]any{
|
|
"client_affinity_enabled": true,
|
|
},
|
|
},
|
|
2: {
|
|
ID: 2,
|
|
Platform: service.PlatformAnthropic,
|
|
Type: service.AccountTypeSetupToken,
|
|
Status: service.StatusActive,
|
|
GroupIDs: []int64{22},
|
|
Extra: map[string]any{
|
|
"client_affinity_enabled": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
cache := &affinityCacheClearRecorder{}
|
|
router := setupAffinityCacheRouter(adminSvc, cache)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"account_ids": []int64{1, 2},
|
|
"extra": map[string]any{
|
|
"client_affinity_enabled": false,
|
|
},
|
|
})
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
router.ServeHTTP(rec, req)
|
|
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Len(t, cache.clearCalls, 2)
|
|
require.Equal(t, int64(1), cache.clearCalls[0].accountID)
|
|
require.Equal(t, []int64{11}, cache.clearCalls[0].groupIDs)
|
|
require.Equal(t, int64(2), cache.clearCalls[1].accountID)
|
|
require.Equal(t, []int64{22}, cache.clearCalls[1].groupIDs)
|
|
}
|
|
|
|
func TestAccountHandlerBulkUpdate_DisablingAffinityWithNewFlagClearsCache(t *testing.T) {
|
|
adminSvc := &affinityCacheAdminService{
|
|
stubAdminService: newStubAdminService(),
|
|
accounts: map[int64]*service.Account{
|
|
1: {
|
|
ID: 1,
|
|
Platform: service.PlatformAnthropic,
|
|
Type: service.AccountTypeOAuth,
|
|
Status: service.StatusActive,
|
|
GroupIDs: []int64{33},
|
|
Extra: map[string]any{
|
|
"affinity_enabled": true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
cache := &affinityCacheClearRecorder{}
|
|
router := setupAffinityCacheRouter(adminSvc, cache)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"account_ids": []int64{1},
|
|
"extra": map[string]any{
|
|
"affinity_enabled": false,
|
|
},
|
|
})
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
router.ServeHTTP(rec, req)
|
|
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Len(t, cache.clearCalls, 1)
|
|
require.Equal(t, int64(1), cache.clearCalls[0].accountID)
|
|
require.Equal(t, []int64{33}, cache.clearCalls[0].groupIDs)
|
|
}
|
|
|
|
func TestAccountHandlerBulkUpdate_PartialFailureStillClearsDisabledAffinityCache(t *testing.T) {
|
|
adminSvc := &affinityCacheAdminService{
|
|
stubAdminService: newStubAdminService(),
|
|
accounts: map[int64]*service.Account{
|
|
1: {
|
|
ID: 1,
|
|
Platform: service.PlatformAnthropic,
|
|
Type: service.AccountTypeOAuth,
|
|
Status: service.StatusActive,
|
|
GroupIDs: []int64{44},
|
|
Extra: map[string]any{
|
|
"affinity_enabled": true,
|
|
},
|
|
},
|
|
2: {
|
|
ID: 2,
|
|
Platform: service.PlatformAnthropic,
|
|
Type: service.AccountTypeSetupToken,
|
|
Status: service.StatusActive,
|
|
GroupIDs: []int64{55},
|
|
Extra: map[string]any{
|
|
"client_affinity_enabled": true,
|
|
},
|
|
},
|
|
},
|
|
bulkResultIDs: []int64{1},
|
|
bulkFailedCount: 1,
|
|
}
|
|
cache := &affinityCacheClearRecorder{}
|
|
router := setupAffinityCacheRouter(adminSvc, cache)
|
|
|
|
body, _ := json.Marshal(map[string]any{
|
|
"account_ids": []int64{1, 2},
|
|
"group_ids": []int64{101},
|
|
"extra": map[string]any{
|
|
"affinity_enabled": false,
|
|
"client_affinity_enabled": false,
|
|
},
|
|
})
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/bulk-update", bytes.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
router.ServeHTTP(rec, req)
|
|
|
|
require.Equal(t, http.StatusOK, rec.Code)
|
|
require.Len(t, cache.clearCalls, 2)
|
|
require.Equal(t, int64(1), cache.clearCalls[0].accountID)
|
|
require.Equal(t, []int64{44, 101}, cache.clearCalls[0].groupIDs)
|
|
require.Equal(t, int64(2), cache.clearCalls[1].accountID)
|
|
require.Equal(t, []int64{55, 101}, cache.clearCalls[1].groupIDs)
|
|
}
|