Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions internal/repository/test/usage_identity_priority_order_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package test

import (
"context"
"reflect"
"testing"
"time"

"cpa-usage-keeper/internal/entities"
"cpa-usage-keeper/internal/repository"
)

func TestAuthFilePrioritySortUsesCPAFileNameAsTieBreak(t *testing.T) {
db := openTestDatabase(t)
now := time.Date(2026, 7, 30, 12, 0, 0, 0, time.UTC)
priority := 5
enabled := false
laterFileName := "codex-bbbbbbbb-user@example.com-free.json"
earlierFileName := "codex-6ac2d0d0-user@example.com-free.json"
rows := []entities.UsageIdentity{
{
Identity: "later-file-name",
Name: "user@example.com",
FileName: &laterFileName,
AuthType: entities.UsageIdentityAuthTypeAuthFile,
Priority: &priority,
Disabled: &enabled,
CreatedAt: now,
UpdatedAt: now,
},
{
Identity: "earlier-file-name",
Name: "user@example.com",
FileName: &earlierFileName,
AuthType: entities.UsageIdentityAuthTypeAuthFile,
Priority: &priority,
Disabled: &enabled,
CreatedAt: now,
UpdatedAt: now,
},
}
if err := db.Create(&rows).Error; err != nil {
t.Fatalf("seed usage identities: %v", err)
}

authType := entities.UsageIdentityAuthTypeAuthFile
activeOnly := true
items, total, _, err := repository.ListActiveUsageIdentitiesPage(context.Background(), db, repository.ListUsageIdentitiesPageRequest{
AuthType: &authType,
ActiveOnly: &activeOnly,
Sort: repository.UsageIdentityPageSortPriority,
Page: 1,
PageSize: 10,
})
if err != nil {
t.Fatalf("list auth files by priority: %v", err)
}
if total != 2 {
t.Fatalf("expected 2 auth files, got %d", total)
}
if got := []string{items[0].Identity, items[1].Identity}; !reflect.DeepEqual(got, []string{"earlier-file-name", "later-file-name"}) {
t.Fatalf("expected equal-priority auth files sorted by CPA file name, got %v", got)
}
}
4 changes: 2 additions & 2 deletions internal/repository/usage_identities.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,10 @@ func applyUsageIdentityTypesFilter(query *gorm.DB, types []string) *gorm.DB {
func applyUsageIdentityPageSort(query *gorm.DB, sort string, authType *entities.UsageIdentityAuthType) *gorm.DB {
switch sort {
case UsageIdentityPageSortPriority:
// Auth Files 的 priority 同分需要稳定按名称排列;AI Provider 只保留同步顺序兜底。
// Auth Files 的 priority 同分按 CPA 文件名排列;AI Provider 只保留同步顺序兜底。
query = query.Order("priority IS NULL ASC").Order("priority DESC")
if authType != nil && *authType == entities.UsageIdentityAuthTypeAuthFile {
query = query.Order("LOWER(name) ASC")
query = query.Order("LOWER(file_name) ASC")
}
return query.Order("id ASC")
case UsageIdentityPageSortTotalTokens:
Expand Down
23 changes: 13 additions & 10 deletions internal/repository/usage_identities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -821,13 +821,13 @@ func TestUsageIdentityListActivePageFiltersEnabledAuthFilesAndOrdersByPriority(t
disabled := true
enabled := false
rows := []entities.UsageIdentity{
{Identity: "default", Name: "Default", AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: nil, Disabled: nil, TotalRequests: 40, TotalTokens: 400, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-5-zeta", Name: "Zeta", AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(5), Disabled: &enabled, TotalRequests: 25, TotalTokens: 250, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-5-alpha", Name: "Alpha", AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(5), Disabled: &enabled, TotalRequests: 22, TotalTokens: 220, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-5-beta-lower", Name: "beta", AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(5), Disabled: &enabled, TotalRequests: 21, TotalTokens: 210, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-1", Name: "Priority 1", AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(1), Disabled: &enabled, TotalRequests: 10, TotalTokens: 100, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-5", Name: "Priority 5", AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(5), Disabled: &enabled, TotalRequests: 20, TotalTokens: 200, CreatedAt: now, UpdatedAt: now},
{Identity: "disabled", Name: "Disabled", AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(0), Disabled: &disabled, TotalRequests: 99, TotalTokens: 999, CreatedAt: now, UpdatedAt: now},
{Identity: "default", Name: "Default", FileName: strPtr("default.json"), AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: nil, Disabled: nil, TotalRequests: 40, TotalTokens: 400, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-5-zeta", Name: "Zeta", FileName: strPtr("zeta.json"), AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(5), Disabled: &enabled, TotalRequests: 25, TotalTokens: 250, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-5-alpha", Name: "Alpha", FileName: strPtr("Alpha.json"), AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(5), Disabled: &enabled, TotalRequests: 22, TotalTokens: 220, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-5-beta-lower", Name: "beta", FileName: strPtr("beta.json"), AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(5), Disabled: &enabled, TotalRequests: 21, TotalTokens: 210, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-1", Name: "Priority 1", FileName: strPtr("priority-1.json"), AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(1), Disabled: &enabled, TotalRequests: 10, TotalTokens: 100, CreatedAt: now, UpdatedAt: now},
{Identity: "priority-5", Name: "Priority 5", FileName: strPtr("priority-5.json"), AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(5), Disabled: &enabled, TotalRequests: 20, TotalTokens: 200, CreatedAt: now, UpdatedAt: now},
{Identity: "disabled", Name: "Disabled", FileName: strPtr("disabled.json"), AuthType: entities.UsageIdentityAuthTypeAuthFile, AuthTypeName: "oauth", Type: "claude", Provider: "Claude", Priority: intPtr(0), Disabled: &disabled, TotalRequests: 99, TotalTokens: 999, CreatedAt: now, UpdatedAt: now},
}
if err := db.Create(&rows).Error; err != nil {
t.Fatalf("seed usage identities: %v", err)
Expand All @@ -843,7 +843,7 @@ func TestUsageIdentityListActivePageFiltersEnabledAuthFilesAndOrdersByPriority(t
t.Fatalf("expected total 6, got %d", total)
}
if got := []string{items[0].Identity, items[1].Identity, items[2].Identity, items[3].Identity, items[4].Identity, items[5].Identity}; !reflect.DeepEqual(got, []string{"priority-5-alpha", "priority-5-beta-lower", "priority-5", "priority-5-zeta", "priority-1", "default"}) {
t.Fatalf("expected enabled auth files sorted by priority desc, name asc case-insensitively, then missing priority last, got %v", got)
t.Fatalf("expected enabled auth files sorted by priority desc, file name asc case-insensitively, then missing priority last, got %v", got)
}
}

Expand Down Expand Up @@ -949,7 +949,7 @@ func TestUsageIdentityListActivePageOrdersAIProvidersByPriorityWithoutNameTieBre
}
}

func TestUsageIdentityPrioritySortUsesPortableNullAndNameOrdering(t *testing.T) {
func TestUsageIdentityPrioritySortUsesPortableNullAndFileNameOrdering(t *testing.T) {
db := openTestDatabase(t).Session(&gorm.Session{DryRun: true})
authType := entities.UsageIdentityAuthTypeAuthFile
var rows []entities.UsageIdentity
Expand All @@ -960,7 +960,7 @@ func TestUsageIdentityPrioritySortUsesPortableNullAndNameOrdering(t *testing.T)
for _, expected := range []string{
"priority IS NULL ASC",
"priority DESC",
"LOWER(name) ASC",
"LOWER(file_name) ASC",
"id ASC",
} {
if !strings.Contains(sql, expected) {
Expand All @@ -970,6 +970,9 @@ func TestUsageIdentityPrioritySortUsesPortableNullAndNameOrdering(t *testing.T)
if strings.Contains(sql, "COLLATE NOCASE") {
t.Fatalf("expected priority sort SQL to avoid sqlite-specific COLLATE NOCASE, got %s", sql)
}
if strings.Contains(sql, "LOWER(name) ASC") {
t.Fatalf("expected auth file priority ties not to use the display name, got %s", sql)
}
}

func TestUsageIdentityListOrdersByAuthTypeNameIDAndIncludesDeletedRows(t *testing.T) {
Expand Down