admin_service_stub_test.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835
  1. package admin
  2. import (
  3. "context"
  4. "strings"
  5. "sync"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/service"
  8. )
  9. type stubAdminService struct {
  10. users []service.User
  11. apiKeys []service.APIKey
  12. groups []service.Group
  13. accounts []service.Account
  14. accountSchedulerScoreFilterAccounts []service.Account
  15. openAISchedulerScorePoolAccounts []service.Account
  16. schedulerScoreFilterCalls int
  17. openAISchedulerScorePoolCalls int
  18. proxies []service.Proxy
  19. proxyCounts []service.ProxyWithAccountCount
  20. redeems []service.RedeemCode
  21. boundAuthIdentity *service.AdminBindAuthIdentityInput
  22. boundAuthIdentityFor int64
  23. createdAccounts []*service.CreateAccountInput
  24. createdGroups []*service.CreateGroupInput
  25. updatedGroups []*service.UpdateGroupInput
  26. deletedGroupIDs []int64
  27. guardedDeletedGroupIDs []int64
  28. deleteGroupIfEmptyErr error
  29. advancedGroupOperationCalls int
  30. lastListGroupsIsExclusive *bool
  31. createdProxies []*service.CreateProxyInput
  32. updatedProxyIDs []int64
  33. updatedProxies []*service.UpdateProxyInput
  34. testedProxyIDs []int64
  35. getUserErr error
  36. createAccountErr error
  37. createSparkShadowErr error
  38. updateAccountErr error
  39. lastUpdateAccountInput *service.UpdateAccountInput
  40. bulkUpdateAccountErr error
  41. lastBulkUpdateAccountInput *service.BulkUpdateAccountsInput
  42. getAccountResult *service.Account
  43. updateAccountCalls int
  44. updateAccountExtraCalls int
  45. checkMixedErr error
  46. lastMixedCheck struct {
  47. accountID int64
  48. platform string
  49. groupIDs []int64
  50. }
  51. lastListAccounts struct {
  52. platform string
  53. accountType string
  54. status string
  55. search string
  56. groupID int64
  57. privacyMode string
  58. sortBy string
  59. sortOrder string
  60. calls int
  61. }
  62. lastListUsers struct {
  63. page int
  64. pageSize int
  65. filters service.UserListFilters
  66. sortBy string
  67. sortOrder string
  68. calls int
  69. }
  70. lastListProxies struct {
  71. protocol string
  72. status string
  73. search string
  74. sortBy string
  75. sortOrder string
  76. calls int
  77. }
  78. lastListRedeemCodes struct {
  79. codeType string
  80. status string
  81. search string
  82. sortBy string
  83. sortOrder string
  84. calls int
  85. }
  86. mu sync.Mutex
  87. }
  88. func newStubAdminService() *stubAdminService {
  89. now := time.Now().UTC()
  90. user := service.User{
  91. ID: 1,
  92. Email: "user@example.com",
  93. Role: service.RoleUser,
  94. Status: service.StatusActive,
  95. CreatedAt: now,
  96. UpdatedAt: now,
  97. }
  98. apiKey := service.APIKey{
  99. ID: 10,
  100. UserID: user.ID,
  101. Key: "sk-test",
  102. Name: "test",
  103. Status: service.StatusActive,
  104. CreatedAt: now,
  105. UpdatedAt: now,
  106. }
  107. group := service.Group{
  108. ID: 2,
  109. Name: "group",
  110. Platform: service.PlatformAnthropic,
  111. Status: service.StatusActive,
  112. CreatedAt: now,
  113. UpdatedAt: now,
  114. }
  115. account := service.Account{
  116. ID: 3,
  117. Name: "account",
  118. Platform: service.PlatformAnthropic,
  119. Type: service.AccountTypeOAuth,
  120. Status: service.StatusActive,
  121. CreatedAt: now,
  122. UpdatedAt: now,
  123. }
  124. proxy := service.Proxy{
  125. ID: 4,
  126. Name: "proxy",
  127. Protocol: "http",
  128. Host: "127.0.0.1",
  129. Port: 8080,
  130. Status: service.StatusActive,
  131. CreatedAt: now,
  132. UpdatedAt: now,
  133. }
  134. redeem := service.RedeemCode{
  135. ID: 5,
  136. Code: "R-TEST",
  137. Type: service.RedeemTypeBalance,
  138. Value: 10,
  139. Status: service.StatusUnused,
  140. CreatedAt: now,
  141. }
  142. return &stubAdminService{
  143. users: []service.User{user},
  144. apiKeys: []service.APIKey{apiKey},
  145. groups: []service.Group{group},
  146. accounts: []service.Account{account},
  147. proxies: []service.Proxy{proxy},
  148. proxyCounts: []service.ProxyWithAccountCount{{Proxy: proxy, AccountCount: 1}},
  149. redeems: []service.RedeemCode{redeem},
  150. }
  151. }
  152. func (s *stubAdminService) ListUsers(ctx context.Context, page, pageSize int, filters service.UserListFilters, sortBy, sortOrder string) ([]service.User, int64, error) {
  153. s.lastListUsers.page = page
  154. s.lastListUsers.pageSize = pageSize
  155. s.lastListUsers.filters = filters
  156. s.lastListUsers.sortBy = sortBy
  157. s.lastListUsers.sortOrder = sortOrder
  158. s.lastListUsers.calls++
  159. return s.users, int64(len(s.users)), nil
  160. }
  161. func (s *stubAdminService) GetUser(ctx context.Context, id int64) (*service.User, error) {
  162. if s.getUserErr != nil {
  163. return nil, s.getUserErr
  164. }
  165. for i := range s.users {
  166. if s.users[i].ID == id {
  167. return &s.users[i], nil
  168. }
  169. }
  170. user := service.User{ID: id, Email: "user@example.com", Status: service.StatusActive}
  171. return &user, nil
  172. }
  173. func (s *stubAdminService) GetUserIncludeDeleted(ctx context.Context, id int64) (*service.User, error) {
  174. return s.GetUser(ctx, id)
  175. }
  176. func (s *stubAdminService) CreateUser(ctx context.Context, input *service.CreateUserInput) (*service.User, error) {
  177. user := service.User{ID: 100, Email: input.Email, Status: service.StatusActive}
  178. return &user, nil
  179. }
  180. func (s *stubAdminService) UpdateUser(ctx context.Context, id int64, input *service.UpdateUserInput) (*service.User, error) {
  181. user := service.User{ID: id, Email: "updated@example.com", Status: service.StatusActive}
  182. return &user, nil
  183. }
  184. func (s *stubAdminService) DeleteUser(ctx context.Context, id int64) error {
  185. return nil
  186. }
  187. func (s *stubAdminService) UpdateUserBalance(ctx context.Context, userID int64, balance float64, operation string, notes string) (*service.User, error) {
  188. user := service.User{ID: userID, Balance: balance, Status: service.StatusActive}
  189. return &user, nil
  190. }
  191. func (s *stubAdminService) BatchUpdateConcurrency(ctx context.Context, userIDs []int64, value int, mode string) (int, error) {
  192. return len(userIDs), nil
  193. }
  194. func (s *stubAdminService) BatchUpdateLimits(ctx context.Context, userIDs []int64, concurrency, rpmLimit *int) (int, error) {
  195. return len(userIDs), nil
  196. }
  197. func (s *stubAdminService) GetUserAPIKeys(ctx context.Context, userID int64, page, pageSize int, sortBy, sortOrder string) ([]service.APIKey, int64, error) {
  198. return s.apiKeys, int64(len(s.apiKeys)), nil
  199. }
  200. func (s *stubAdminService) GetUserUsageStats(ctx context.Context, userID int64, period string) (any, error) {
  201. return map[string]any{"user_id": userID}, nil
  202. }
  203. func (s *stubAdminService) GetUserRPMStatus(ctx context.Context, userID int64) (*service.UserRPMStatus, error) {
  204. user, err := s.GetUser(ctx, userID)
  205. if err != nil {
  206. return nil, err
  207. }
  208. return &service.UserRPMStatus{
  209. UserRPMUsed: 0,
  210. UserRPMLimit: user.RPMLimit,
  211. }, nil
  212. }
  213. func (s *stubAdminService) BindUserAuthIdentity(ctx context.Context, userID int64, input service.AdminBindAuthIdentityInput) (*service.AdminBoundAuthIdentity, error) {
  214. s.boundAuthIdentityFor = userID
  215. copied := input
  216. if input.Metadata != nil {
  217. copied.Metadata = map[string]any{}
  218. for key, value := range input.Metadata {
  219. copied.Metadata[key] = value
  220. }
  221. }
  222. if input.Channel != nil {
  223. channel := *input.Channel
  224. if input.Channel.Metadata != nil {
  225. channel.Metadata = map[string]any{}
  226. for key, value := range input.Channel.Metadata {
  227. channel.Metadata[key] = value
  228. }
  229. }
  230. copied.Channel = &channel
  231. }
  232. s.boundAuthIdentity = &copied
  233. now := time.Now().UTC()
  234. result := &service.AdminBoundAuthIdentity{
  235. UserID: userID,
  236. ProviderType: input.ProviderType,
  237. ProviderKey: input.ProviderKey,
  238. ProviderSubject: input.ProviderSubject,
  239. VerifiedAt: &now,
  240. Issuer: input.Issuer,
  241. Metadata: input.Metadata,
  242. CreatedAt: now,
  243. UpdatedAt: now,
  244. }
  245. if input.Channel != nil {
  246. result.Channel = &service.AdminBoundAuthIdentityChannel{
  247. Channel: input.Channel.Channel,
  248. ChannelAppID: input.Channel.ChannelAppID,
  249. ChannelSubject: input.Channel.ChannelSubject,
  250. Metadata: input.Channel.Metadata,
  251. CreatedAt: now,
  252. UpdatedAt: now,
  253. }
  254. }
  255. return result, nil
  256. }
  257. func (s *stubAdminService) ListGroups(ctx context.Context, page, pageSize int, platform, status, search string, isExclusive *bool, sortBy, sortOrder string) ([]service.Group, int64, error) {
  258. s.lastListGroupsIsExclusive = isExclusive
  259. return s.groups, int64(len(s.groups)), nil
  260. }
  261. func (s *stubAdminService) GetAllGroups(ctx context.Context) ([]service.Group, error) {
  262. return s.groups, nil
  263. }
  264. func (s *stubAdminService) GetAllGroupsByPlatform(ctx context.Context, platform string) ([]service.Group, error) {
  265. return s.groups, nil
  266. }
  267. func (s *stubAdminService) GetAllGroupsIncludingInactive(ctx context.Context) ([]service.Group, error) {
  268. return s.groups, nil
  269. }
  270. func (s *stubAdminService) GetGroup(ctx context.Context, id int64) (*service.Group, error) {
  271. group := service.Group{ID: id, Name: "group", Status: service.StatusActive}
  272. return &group, nil
  273. }
  274. func (s *stubAdminService) GetGroupModelsListCandidates(ctx context.Context, id int64, platform string) ([]string, error) {
  275. if platform == service.PlatformOpenAI {
  276. return []string{"gpt-5.5", "gpt-5.4"}, nil
  277. }
  278. return []string{"claude-sonnet-4-6"}, nil
  279. }
  280. func (s *stubAdminService) ListCompositeRoutes(ctx context.Context, groupID int64) ([]service.CompositeModelRoute, error) {
  281. s.advancedGroupOperationCalls++
  282. return []service.CompositeModelRoute{
  283. {
  284. ID: 1,
  285. GroupID: groupID,
  286. PublicModel: "openrouter/gpt-5",
  287. MatchType: service.CompositeRouteMatchExact,
  288. TargetPlatform: service.PlatformOpenAI,
  289. UpstreamModel: "gpt-5",
  290. Endpoint: service.CompositeRouteEndpointAny,
  291. Priority: 100,
  292. Enabled: true,
  293. },
  294. }, nil
  295. }
  296. func (s *stubAdminService) CreateCompositeRoute(ctx context.Context, groupID int64, input service.CompositeRouteInput) (*service.CompositeModelRoute, error) {
  297. s.advancedGroupOperationCalls++
  298. return &service.CompositeModelRoute{
  299. ID: 1,
  300. GroupID: groupID,
  301. PublicModel: input.PublicModel,
  302. MatchType: input.MatchType,
  303. TargetPlatform: input.TargetPlatform,
  304. UpstreamModel: input.UpstreamModel,
  305. Endpoint: input.Endpoint,
  306. Priority: input.Priority,
  307. Enabled: input.Enabled,
  308. Notes: input.Notes,
  309. }, nil
  310. }
  311. func (s *stubAdminService) UpdateCompositeRoute(ctx context.Context, groupID, routeID int64, input service.CompositeRouteInput) (*service.CompositeModelRoute, error) {
  312. s.advancedGroupOperationCalls++
  313. return &service.CompositeModelRoute{
  314. ID: routeID,
  315. GroupID: groupID,
  316. PublicModel: input.PublicModel,
  317. MatchType: input.MatchType,
  318. TargetPlatform: input.TargetPlatform,
  319. UpstreamModel: input.UpstreamModel,
  320. Endpoint: input.Endpoint,
  321. Priority: input.Priority,
  322. Enabled: input.Enabled,
  323. Notes: input.Notes,
  324. }, nil
  325. }
  326. func (s *stubAdminService) DeleteCompositeRoute(ctx context.Context, groupID, routeID int64) error {
  327. s.advancedGroupOperationCalls++
  328. return nil
  329. }
  330. func (s *stubAdminService) PreviewCompositeRoute(ctx context.Context, groupID int64, input service.CompositeRoutePreviewRequest) (*service.CompositeRouteDecision, error) {
  331. s.advancedGroupOperationCalls++
  332. decision, err := service.NewCompositeRouteResolver(nil).Resolve(ctx, groupID, input.Model, input.Endpoint)
  333. if err != nil {
  334. return nil, err
  335. }
  336. return &decision, nil
  337. }
  338. func (s *stubAdminService) CreateGroup(ctx context.Context, input *service.CreateGroupInput) (*service.Group, error) {
  339. s.createdGroups = append(s.createdGroups, input)
  340. group := service.Group{ID: 200, Name: input.Name, Status: service.StatusActive}
  341. return &group, nil
  342. }
  343. func (s *stubAdminService) DuplicateGroup(ctx context.Context, id int64, actorScope, operationKey string) (*service.Group, error) {
  344. s.advancedGroupOperationCalls++
  345. group := service.Group{ID: 201, Name: "group (Copy)", Status: "inactive"}
  346. return &group, nil
  347. }
  348. func (s *stubAdminService) RecoverDuplicateGroup(ctx context.Context, id int64, actorScope, operationKey string) (*service.Group, error) {
  349. return nil, nil
  350. }
  351. func (s *stubAdminService) UpdateGroup(ctx context.Context, id int64, input *service.UpdateGroupInput) (*service.Group, error) {
  352. s.updatedGroups = append(s.updatedGroups, input)
  353. group := service.Group{ID: id, Name: input.Name, Status: service.StatusActive}
  354. return &group, nil
  355. }
  356. func (s *stubAdminService) DeleteGroup(ctx context.Context, id int64) error {
  357. s.deletedGroupIDs = append(s.deletedGroupIDs, id)
  358. return nil
  359. }
  360. func (s *stubAdminService) DeleteGroupIfEmpty(ctx context.Context, id int64) error {
  361. s.guardedDeletedGroupIDs = append(s.guardedDeletedGroupIDs, id)
  362. return s.deleteGroupIfEmptyErr
  363. }
  364. func (s *stubAdminService) GetGroupAPIKeys(ctx context.Context, groupID int64, page, pageSize int) ([]service.APIKey, int64, error) {
  365. return s.apiKeys, int64(len(s.apiKeys)), nil
  366. }
  367. func (s *stubAdminService) GetGroupRateMultipliers(_ context.Context, _ int64) ([]service.UserGroupRateEntry, error) {
  368. return nil, nil
  369. }
  370. func (s *stubAdminService) ClearGroupRateMultipliers(_ context.Context, _ int64) error {
  371. s.advancedGroupOperationCalls++
  372. return nil
  373. }
  374. func (s *stubAdminService) BatchSetGroupRateMultipliers(_ context.Context, _ int64, _ []service.GroupRateMultiplierInput) error {
  375. s.advancedGroupOperationCalls++
  376. return nil
  377. }
  378. func (s *stubAdminService) ClearGroupRPMOverrides(_ context.Context, _ int64) error {
  379. return nil
  380. }
  381. func (s *stubAdminService) BatchSetGroupRPMOverrides(_ context.Context, _ int64, _ []service.GroupRPMOverrideInput) error {
  382. s.advancedGroupOperationCalls++
  383. return nil
  384. }
  385. func (s *stubAdminService) ListAccounts(ctx context.Context, page, pageSize int, platform, accountType, status, search string, groupID int64, privacyMode string, sortBy, sortOrder string) ([]service.Account, int64, error) {
  386. s.lastListAccounts.platform = platform
  387. s.lastListAccounts.accountType = accountType
  388. s.lastListAccounts.status = status
  389. s.lastListAccounts.search = search
  390. s.lastListAccounts.groupID = groupID
  391. s.lastListAccounts.privacyMode = privacyMode
  392. s.lastListAccounts.sortBy = sortBy
  393. s.lastListAccounts.sortOrder = sortOrder
  394. s.lastListAccounts.calls++
  395. accounts := s.accounts
  396. total := len(accounts)
  397. if page < 1 {
  398. page = 1
  399. }
  400. if pageSize < 1 {
  401. pageSize = total
  402. }
  403. start := (page - 1) * pageSize
  404. if start >= total {
  405. return []service.Account{}, int64(total), nil
  406. }
  407. end := start + pageSize
  408. if end > total {
  409. end = total
  410. }
  411. return accounts[start:end], int64(total), nil
  412. }
  413. func (s *stubAdminService) ListAccountsForSchedulerScoreFilter(_ context.Context, platform, accountType, status, search string, groupID int64, privacyMode string) ([]service.Account, error) {
  414. s.schedulerScoreFilterCalls++
  415. if s.accountSchedulerScoreFilterAccounts != nil {
  416. return s.accountSchedulerScoreFilterAccounts, nil
  417. }
  418. return s.accounts, nil
  419. }
  420. func (s *stubAdminService) ListOpenAISchedulableAccountsForSchedulerScore(_ context.Context, groupID *int64) ([]service.Account, error) {
  421. s.openAISchedulerScorePoolCalls++
  422. accounts := s.openAISchedulerScorePoolAccounts
  423. if accounts == nil {
  424. accounts = s.accounts
  425. }
  426. out := make([]service.Account, 0, len(accounts))
  427. for _, account := range accounts {
  428. if account.Platform != service.PlatformOpenAI || !account.IsSchedulable() {
  429. continue
  430. }
  431. if groupID == nil {
  432. if len(account.AccountGroups) == 0 && len(account.GroupIDs) == 0 {
  433. out = append(out, account)
  434. }
  435. continue
  436. }
  437. for _, accountGroup := range account.AccountGroups {
  438. if accountGroup.GroupID == *groupID {
  439. out = append(out, account)
  440. break
  441. }
  442. }
  443. }
  444. return out, nil
  445. }
  446. func (s *stubAdminService) GetAccount(ctx context.Context, id int64) (*service.Account, error) {
  447. if s.getAccountResult != nil {
  448. return s.getAccountResult, nil
  449. }
  450. account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
  451. return &account, nil
  452. }
  453. func (s *stubAdminService) GetAccountsByIDs(ctx context.Context, ids []int64) ([]*service.Account, error) {
  454. out := make([]*service.Account, 0, len(ids))
  455. for _, id := range ids {
  456. account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
  457. out = append(out, &account)
  458. }
  459. return out, nil
  460. }
  461. func (s *stubAdminService) CreateAccount(ctx context.Context, input *service.CreateAccountInput) (*service.Account, error) {
  462. s.mu.Lock()
  463. s.createdAccounts = append(s.createdAccounts, input)
  464. s.mu.Unlock()
  465. if s.createAccountErr != nil {
  466. return nil, s.createAccountErr
  467. }
  468. account := service.Account{ID: 300, Name: input.Name, Status: service.StatusActive}
  469. return &account, nil
  470. }
  471. func (s *stubAdminService) ValidateAccountGroupBindings(context.Context, []int64) error { return nil }
  472. func (s *stubAdminService) DuplicateAccount(ctx context.Context, id int64, actorScope, operationKey string) (*service.Account, error) {
  473. account := service.Account{ID: 301, Name: "account (Copy)", Status: service.StatusActive, Schedulable: false}
  474. return &account, nil
  475. }
  476. func (s *stubAdminService) RecoverDuplicateAccount(ctx context.Context, id int64, actorScope, operationKey string) (*service.Account, error) {
  477. return nil, nil
  478. }
  479. func (s *stubAdminService) UpdateAccount(ctx context.Context, id int64, input *service.UpdateAccountInput) (*service.Account, error) {
  480. s.updateAccountCalls++
  481. s.lastUpdateAccountInput = input
  482. if s.updateAccountErr != nil {
  483. return nil, s.updateAccountErr
  484. }
  485. account := service.Account{ID: id, Name: input.Name, Status: service.StatusActive}
  486. return &account, nil
  487. }
  488. func (s *stubAdminService) UpdateAccountExtra(ctx context.Context, id int64, updates map[string]any) error {
  489. s.updateAccountExtraCalls++
  490. return nil
  491. }
  492. func (s *stubAdminService) DeleteAccount(ctx context.Context, id int64) error {
  493. return nil
  494. }
  495. func (s *stubAdminService) RefreshAccountCredentials(ctx context.Context, id int64) (*service.Account, error) {
  496. account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
  497. return &account, nil
  498. }
  499. func (s *stubAdminService) ClearAccountError(ctx context.Context, id int64) (*service.Account, error) {
  500. account := service.Account{ID: id, Name: "account", Status: service.StatusActive}
  501. return &account, nil
  502. }
  503. func (s *stubAdminService) SetAccountError(ctx context.Context, id int64, errorMsg string) error {
  504. return nil
  505. }
  506. func (s *stubAdminService) SetAccountSchedulable(ctx context.Context, id int64, schedulable bool) (*service.Account, error) {
  507. account := service.Account{ID: id, Name: "account", Status: service.StatusActive, Schedulable: schedulable}
  508. return &account, nil
  509. }
  510. func (s *stubAdminService) BulkUpdateAccounts(ctx context.Context, input *service.BulkUpdateAccountsInput) (*service.BulkUpdateAccountsResult, error) {
  511. s.lastBulkUpdateAccountInput = input
  512. if s.bulkUpdateAccountErr != nil {
  513. return nil, s.bulkUpdateAccountErr
  514. }
  515. return &service.BulkUpdateAccountsResult{Success: len(input.AccountIDs), Failed: 0, SuccessIDs: input.AccountIDs}, nil
  516. }
  517. func (s *stubAdminService) CheckMixedChannelRisk(ctx context.Context, currentAccountID int64, currentAccountPlatform string, groupIDs []int64) error {
  518. s.lastMixedCheck.accountID = currentAccountID
  519. s.lastMixedCheck.platform = currentAccountPlatform
  520. s.lastMixedCheck.groupIDs = append([]int64(nil), groupIDs...)
  521. return s.checkMixedErr
  522. }
  523. func (s *stubAdminService) ListProxies(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]service.Proxy, int64, error) {
  524. s.lastListProxies.protocol = protocol
  525. s.lastListProxies.status = status
  526. s.lastListProxies.search = search
  527. s.lastListProxies.sortBy = sortBy
  528. s.lastListProxies.sortOrder = sortOrder
  529. s.lastListProxies.calls++
  530. search = strings.TrimSpace(strings.ToLower(search))
  531. filtered := make([]service.Proxy, 0, len(s.proxies))
  532. for _, proxy := range s.proxies {
  533. if protocol != "" && proxy.Protocol != protocol {
  534. continue
  535. }
  536. if status != "" && proxy.Status != status {
  537. continue
  538. }
  539. if search != "" {
  540. name := strings.ToLower(proxy.Name)
  541. host := strings.ToLower(proxy.Host)
  542. if !strings.Contains(name, search) && !strings.Contains(host, search) {
  543. continue
  544. }
  545. }
  546. filtered = append(filtered, proxy)
  547. }
  548. return filtered, int64(len(filtered)), nil
  549. }
  550. func (s *stubAdminService) ListProxiesWithAccountCount(ctx context.Context, page, pageSize int, protocol, status, search string, sortBy, sortOrder string) ([]service.ProxyWithAccountCount, int64, error) {
  551. return s.proxyCounts, int64(len(s.proxyCounts)), nil
  552. }
  553. func (s *stubAdminService) GetAllProxies(ctx context.Context) ([]service.Proxy, error) {
  554. return s.proxies, nil
  555. }
  556. func (s *stubAdminService) GetAllProxiesWithAccountCount(ctx context.Context) ([]service.ProxyWithAccountCount, error) {
  557. return s.proxyCounts, nil
  558. }
  559. func (s *stubAdminService) GetProxy(ctx context.Context, id int64) (*service.Proxy, error) {
  560. for i := range s.proxies {
  561. proxy := s.proxies[i]
  562. if proxy.ID == id {
  563. return &proxy, nil
  564. }
  565. }
  566. proxy := service.Proxy{ID: id, Name: "proxy", Status: service.StatusActive}
  567. return &proxy, nil
  568. }
  569. func (s *stubAdminService) GetProxiesByIDs(ctx context.Context, ids []int64) ([]service.Proxy, error) {
  570. if len(ids) == 0 {
  571. return []service.Proxy{}, nil
  572. }
  573. out := make([]service.Proxy, 0, len(ids))
  574. seen := make(map[int64]struct{}, len(ids))
  575. for _, id := range ids {
  576. seen[id] = struct{}{}
  577. }
  578. for i := range s.proxies {
  579. proxy := s.proxies[i]
  580. if _, ok := seen[proxy.ID]; ok {
  581. out = append(out, proxy)
  582. }
  583. }
  584. return out, nil
  585. }
  586. func (s *stubAdminService) CreateProxy(ctx context.Context, input *service.CreateProxyInput) (*service.Proxy, error) {
  587. s.mu.Lock()
  588. s.createdProxies = append(s.createdProxies, input)
  589. s.mu.Unlock()
  590. proxy := service.Proxy{ID: 400, Name: input.Name, Status: service.StatusActive}
  591. return &proxy, nil
  592. }
  593. func (s *stubAdminService) UpdateProxy(ctx context.Context, id int64, input *service.UpdateProxyInput) (*service.Proxy, error) {
  594. s.mu.Lock()
  595. s.updatedProxyIDs = append(s.updatedProxyIDs, id)
  596. s.updatedProxies = append(s.updatedProxies, input)
  597. s.mu.Unlock()
  598. proxy := service.Proxy{ID: id, Name: input.Name, Status: service.StatusActive}
  599. return &proxy, nil
  600. }
  601. func (s *stubAdminService) DeleteProxy(ctx context.Context, id int64) error {
  602. return nil
  603. }
  604. func (s *stubAdminService) BatchDeleteProxies(ctx context.Context, ids []int64) (*service.ProxyBatchDeleteResult, error) {
  605. return &service.ProxyBatchDeleteResult{DeletedIDs: ids}, nil
  606. }
  607. func (s *stubAdminService) GetProxyAccounts(ctx context.Context, proxyID int64) ([]service.ProxyAccountSummary, error) {
  608. return []service.ProxyAccountSummary{{ID: 1, Name: "account"}}, nil
  609. }
  610. func (s *stubAdminService) CheckProxyExists(ctx context.Context, host string, port int, username, password string) (bool, error) {
  611. return false, nil
  612. }
  613. func (s *stubAdminService) TestProxy(ctx context.Context, id int64) (*service.ProxyTestResult, error) {
  614. s.mu.Lock()
  615. s.testedProxyIDs = append(s.testedProxyIDs, id)
  616. s.mu.Unlock()
  617. return &service.ProxyTestResult{Success: true, Message: "ok"}, nil
  618. }
  619. func (s *stubAdminService) CheckProxyQuality(ctx context.Context, id int64) (*service.ProxyQualityCheckResult, error) {
  620. return &service.ProxyQualityCheckResult{
  621. ProxyID: id,
  622. Score: 95,
  623. Grade: "A",
  624. Summary: "通过 5 项,告警 0 项,失败 0 项,挑战 0 项",
  625. PassedCount: 5,
  626. WarnCount: 0,
  627. FailedCount: 0,
  628. ChallengeCount: 0,
  629. CheckedAt: time.Now().Unix(),
  630. Items: []service.ProxyQualityCheckItem{
  631. {Target: "base_connectivity", Status: "pass", Message: "ok"},
  632. {Target: "openai", Status: "pass", HTTPStatus: 401},
  633. {Target: "anthropic", Status: "pass", HTTPStatus: 401},
  634. {Target: "gemini", Status: "pass", HTTPStatus: 200},
  635. },
  636. }, nil
  637. }
  638. func (s *stubAdminService) ListRedeemCodes(ctx context.Context, page, pageSize int, codeType, status, search string, sortBy, sortOrder string) ([]service.RedeemCode, int64, error) {
  639. s.lastListRedeemCodes.codeType = codeType
  640. s.lastListRedeemCodes.status = status
  641. s.lastListRedeemCodes.search = search
  642. s.lastListRedeemCodes.sortBy = sortBy
  643. s.lastListRedeemCodes.sortOrder = sortOrder
  644. s.lastListRedeemCodes.calls++
  645. return s.redeems, int64(len(s.redeems)), nil
  646. }
  647. func (s *stubAdminService) GetRedeemCode(ctx context.Context, id int64) (*service.RedeemCode, error) {
  648. code := service.RedeemCode{ID: id, Code: "R-TEST", Status: service.StatusUnused}
  649. return &code, nil
  650. }
  651. func (s *stubAdminService) GenerateRedeemCodes(ctx context.Context, input *service.GenerateRedeemCodesInput) ([]service.RedeemCode, error) {
  652. return s.redeems, nil
  653. }
  654. func (s *stubAdminService) DeleteRedeemCode(ctx context.Context, id int64) error {
  655. return nil
  656. }
  657. func (s *stubAdminService) BatchDeleteRedeemCodes(ctx context.Context, ids []int64) (int64, error) {
  658. return int64(len(ids)), nil
  659. }
  660. func (s *stubAdminService) ExpireRedeemCode(ctx context.Context, id int64) (*service.RedeemCode, error) {
  661. code := service.RedeemCode{ID: id, Code: "R-TEST", Status: service.StatusUsed}
  662. return &code, nil
  663. }
  664. func (s *stubAdminService) GetUserBalanceHistory(ctx context.Context, userID int64, page, pageSize int, codeType string) ([]service.RedeemCode, int64, float64, error) {
  665. return s.redeems, int64(len(s.redeems)), 100.0, nil
  666. }
  667. func (s *stubAdminService) UpdateGroupSortOrders(ctx context.Context, updates []service.GroupSortOrderUpdate) error {
  668. return nil
  669. }
  670. func (s *stubAdminService) AdminUpdateAPIKeyGroupID(ctx context.Context, keyID int64, groupID *int64) (*service.AdminUpdateAPIKeyGroupIDResult, error) {
  671. for i := range s.apiKeys {
  672. if s.apiKeys[i].ID == keyID {
  673. k := s.apiKeys[i]
  674. if groupID != nil {
  675. if *groupID == 0 {
  676. k.GroupID = nil
  677. } else {
  678. gid := *groupID
  679. k.GroupID = &gid
  680. }
  681. }
  682. return &service.AdminUpdateAPIKeyGroupIDResult{APIKey: &k}, nil
  683. }
  684. }
  685. return nil, service.ErrAPIKeyNotFound
  686. }
  687. func (s *stubAdminService) AdminResetAPIKeyRateLimitUsage(ctx context.Context, keyID int64) (*service.APIKey, error) {
  688. for i := range s.apiKeys {
  689. if s.apiKeys[i].ID == keyID {
  690. s.apiKeys[i].Usage5h = 0
  691. s.apiKeys[i].Usage1d = 0
  692. s.apiKeys[i].Usage7d = 0
  693. s.apiKeys[i].Window5hStart = nil
  694. s.apiKeys[i].Window1dStart = nil
  695. s.apiKeys[i].Window7dStart = nil
  696. k := s.apiKeys[i]
  697. return &k, nil
  698. }
  699. }
  700. return nil, service.ErrAPIKeyNotFound
  701. }
  702. func (s *stubAdminService) ResetAccountQuota(ctx context.Context, id int64) error {
  703. return nil
  704. }
  705. func (s *stubAdminService) EnsureOpenAIPrivacy(ctx context.Context, account *service.Account) string {
  706. return ""
  707. }
  708. func (s *stubAdminService) EnsureAntigravityPrivacy(ctx context.Context, account *service.Account) string {
  709. return ""
  710. }
  711. func (s *stubAdminService) ForceOpenAIPrivacy(ctx context.Context, account *service.Account) string {
  712. return ""
  713. }
  714. func (s *stubAdminService) ForceAntigravityPrivacy(ctx context.Context, account *service.Account) string {
  715. return ""
  716. }
  717. func (s *stubAdminService) ReplaceUserGroup(ctx context.Context, userID, oldGroupID, newGroupID int64) (*service.ReplaceUserGroupResult, error) {
  718. return &service.ReplaceUserGroupResult{MigratedKeys: 0}, nil
  719. }
  720. func (s *stubAdminService) RevertAccountProxyFallback(ctx context.Context, id int64) error {
  721. return nil
  722. }
  723. func (s *stubAdminService) CreateShadow(ctx context.Context, parentID int64, opts service.ShadowOptions) (*service.Account, error) {
  724. if s.createSparkShadowErr != nil {
  725. return nil, s.createSparkShadowErr
  726. }
  727. pid := parentID
  728. return &service.Account{
  729. ID: 9001,
  730. Name: opts.Name,
  731. Platform: service.PlatformOpenAI,
  732. Type: service.AccountTypeOAuth,
  733. Priority: opts.Priority,
  734. Concurrency: opts.Concurrency,
  735. GroupIDs: opts.GroupIDs,
  736. ParentAccountID: &pid,
  737. QuotaDimension: service.QuotaDimensionSpark,
  738. Credentials: map[string]any{},
  739. Extra: map[string]any{},
  740. }, nil
  741. }
  742. // Ensure stub implements interface.
  743. var _ service.AdminService = (*stubAdminService)(nil)