| package sql_test |
|
|
| import ( |
| "context" |
| "testing" |
|
|
| "ccLoad/internal/model" |
| ) |
|
|
| func TestAPIKey_CreateAndGet(t *testing.T) { |
| t.Parallel() |
|
|
| store := newTestStore(t, "apikey.db") |
|
|
| ctx := context.Background() |
| channelID := createTestChannel(t, ctx, store, "apikey-test-channel") |
|
|
| |
| keys := []*model.APIKey{ |
| {ChannelID: channelID, KeyIndex: 0, APIKey: "sk-key-0", KeyStrategy: model.KeyStrategySequential}, |
| {ChannelID: channelID, KeyIndex: 1, APIKey: "sk-key-1", KeyStrategy: model.KeyStrategySequential}, |
| {ChannelID: channelID, KeyIndex: 2, APIKey: "sk-key-2", KeyStrategy: model.KeyStrategySequential}, |
| } |
| if err := store.CreateAPIKeysBatch(ctx, keys); err != nil { |
| t.Fatalf("create api keys batch: %v", err) |
| } |
|
|
| |
| key, err := store.GetAPIKey(ctx, channelID, 1) |
| if err != nil { |
| t.Fatalf("get api key: %v", err) |
| } |
| if key.APIKey != "sk-key-1" { |
| t.Errorf("api key: got %q, want %q", key.APIKey, "sk-key-1") |
| } |
| if key.KeyIndex != 1 { |
| t.Errorf("key index: got %d, want %d", key.KeyIndex, 1) |
| } |
|
|
| |
| allKeys, err := store.GetAPIKeys(ctx, channelID) |
| if err != nil { |
| t.Fatalf("get api keys: %v", err) |
| } |
| if len(allKeys) != 3 { |
| t.Errorf("expected 3 keys, got %d", len(allKeys)) |
| } |
| } |
|
|
| func TestAPIKey_UpdateStrategy(t *testing.T) { |
| t.Parallel() |
|
|
| store := newTestStore(t, "strategy.db") |
|
|
| ctx := context.Background() |
| channelID := createTestChannel(t, ctx, store, "strategy-test-channel") |
|
|
| |
| keys := []*model.APIKey{ |
| {ChannelID: channelID, KeyIndex: 0, APIKey: "sk-key", KeyStrategy: model.KeyStrategySequential}, |
| } |
| if err := store.CreateAPIKeysBatch(ctx, keys); err != nil { |
| t.Fatalf("create api keys batch: %v", err) |
| } |
|
|
| |
| if err := store.UpdateAPIKeysStrategy(ctx, channelID, model.KeyStrategyRoundRobin); err != nil { |
| t.Fatalf("update api keys strategy: %v", err) |
| } |
|
|
| |
| key, err := store.GetAPIKey(ctx, channelID, 0) |
| if err != nil { |
| t.Fatalf("get api key: %v", err) |
| } |
| if key.KeyStrategy != model.KeyStrategyRoundRobin { |
| t.Errorf("strategy: got %q, want %q", key.KeyStrategy, model.KeyStrategyRoundRobin) |
| } |
| } |
|
|
| func TestAPIKey_Delete(t *testing.T) { |
| t.Parallel() |
|
|
| store := newTestStore(t, "delete.db") |
|
|
| ctx := context.Background() |
| channelID := createTestChannel(t, ctx, store, "delete-test-channel") |
|
|
| |
| keys := []*model.APIKey{ |
| {ChannelID: channelID, KeyIndex: 0, APIKey: "sk-key-0", KeyStrategy: model.KeyStrategySequential}, |
| {ChannelID: channelID, KeyIndex: 1, APIKey: "sk-key-1", KeyStrategy: model.KeyStrategySequential}, |
| {ChannelID: channelID, KeyIndex: 2, APIKey: "sk-key-2", KeyStrategy: model.KeyStrategySequential}, |
| } |
| if err := store.CreateAPIKeysBatch(ctx, keys); err != nil { |
| t.Fatalf("create api keys batch: %v", err) |
| } |
|
|
| |
| if err := store.DeleteAPIKey(ctx, channelID, 1); err != nil { |
| t.Fatalf("delete api key: %v", err) |
| } |
|
|
| |
| allKeys, err := store.GetAPIKeys(ctx, channelID) |
| if err != nil { |
| t.Fatalf("get api keys: %v", err) |
| } |
| if len(allKeys) != 2 { |
| t.Errorf("expected 2 keys after delete, got %d", len(allKeys)) |
| } |
| } |
|
|
| func TestAPIKey_CompactIndices(t *testing.T) { |
| t.Parallel() |
|
|
| store := newTestStore(t, "compact.db") |
|
|
| ctx := context.Background() |
| channelID := createTestChannel(t, ctx, store, "compact-test-channel") |
|
|
| |
| keys := []*model.APIKey{ |
| {ChannelID: channelID, KeyIndex: 0, APIKey: "sk-key-0", KeyStrategy: model.KeyStrategySequential}, |
| {ChannelID: channelID, KeyIndex: 1, APIKey: "sk-key-1", KeyStrategy: model.KeyStrategySequential}, |
| {ChannelID: channelID, KeyIndex: 2, APIKey: "sk-key-2", KeyStrategy: model.KeyStrategySequential}, |
| } |
| if err := store.CreateAPIKeysBatch(ctx, keys); err != nil { |
| t.Fatalf("create api keys batch: %v", err) |
| } |
|
|
| |
| if err := store.DeleteAPIKey(ctx, channelID, 1); err != nil { |
| t.Fatalf("delete api key: %v", err) |
| } |
|
|
| |
| if err := store.CompactKeyIndices(ctx, channelID, 1); err != nil { |
| t.Fatalf("compact key indices: %v", err) |
| } |
|
|
| |
| allKeys, err := store.GetAPIKeys(ctx, channelID) |
| if err != nil { |
| t.Fatalf("get api keys: %v", err) |
| } |
| if len(allKeys) != 2 { |
| t.Errorf("expected 2 keys, got %d", len(allKeys)) |
| } |
|
|
| |
| for i, key := range allKeys { |
| if key.KeyIndex != i { |
| t.Errorf("key %d: expected index %d, got %d", i, i, key.KeyIndex) |
| } |
| } |
| } |
|
|
| func TestAPIKey_DeleteAll(t *testing.T) { |
| t.Parallel() |
|
|
| store := newTestStore(t, "delete_all.db") |
|
|
| ctx := context.Background() |
| channelID := createTestChannel(t, ctx, store, "delete-all-test-channel") |
|
|
| |
| keys := []*model.APIKey{ |
| {ChannelID: channelID, KeyIndex: 0, APIKey: "sk-key-0", KeyStrategy: model.KeyStrategySequential}, |
| {ChannelID: channelID, KeyIndex: 1, APIKey: "sk-key-1", KeyStrategy: model.KeyStrategySequential}, |
| } |
| if err := store.CreateAPIKeysBatch(ctx, keys); err != nil { |
| t.Fatalf("create api keys batch: %v", err) |
| } |
|
|
| |
| if err := store.DeleteAllAPIKeys(ctx, channelID); err != nil { |
| t.Fatalf("delete all api keys: %v", err) |
| } |
|
|
| |
| allKeys, err := store.GetAPIKeys(ctx, channelID) |
| if err != nil { |
| t.Fatalf("get api keys: %v", err) |
| } |
| if len(allKeys) != 0 { |
| t.Errorf("expected 0 keys, got %d", len(allKeys)) |
| } |
| } |
|
|
| func TestAPIKey_GetAllAPIKeys(t *testing.T) { |
| t.Parallel() |
|
|
| store := newTestStore(t, "get_all.db") |
|
|
| ctx := context.Background() |
|
|
| |
| channelID1 := createTestChannel(t, ctx, store, "channel-1") |
| channelID2 := createTestChannel(t, ctx, store, "channel-2") |
|
|
| |
| keys1 := []*model.APIKey{ |
| {ChannelID: channelID1, KeyIndex: 0, APIKey: "sk-ch1-key-0", KeyStrategy: model.KeyStrategySequential}, |
| {ChannelID: channelID1, KeyIndex: 1, APIKey: "sk-ch1-key-1", KeyStrategy: model.KeyStrategySequential}, |
| } |
| if err := store.CreateAPIKeysBatch(ctx, keys1); err != nil { |
| t.Fatalf("create api keys for channel 1: %v", err) |
| } |
|
|
| keys2 := []*model.APIKey{ |
| {ChannelID: channelID2, KeyIndex: 0, APIKey: "sk-ch2-key-0", KeyStrategy: model.KeyStrategyRoundRobin}, |
| } |
| if err := store.CreateAPIKeysBatch(ctx, keys2); err != nil { |
| t.Fatalf("create api keys for channel 2: %v", err) |
| } |
|
|
| |
| allKeys, err := store.GetAllAPIKeys(ctx) |
| if err != nil { |
| t.Fatalf("get all api keys: %v", err) |
| } |
| if countAPIKeys(allKeys) != 3 { |
| t.Errorf("expected 3 total keys, got %d", countAPIKeys(allKeys)) |
| } |
| } |
|
|
| func TestAPIKey_ImportChannelBatch(t *testing.T) { |
| t.Parallel() |
|
|
| store := newTestStore(t, "import.db") |
|
|
| ctx := context.Background() |
|
|
| |
| channels := []*model.ChannelWithKeys{ |
| { |
| Config: &model.Config{ |
| Name: "imported-channel-1", |
| URL: "https://api1.example.com", |
| Priority: 10, |
| Enabled: true, |
| ScheduledCheckEnabled: true, |
| ChannelType: "openai", |
| ModelEntries: []model.ModelEntry{ |
| {Model: "gpt-4"}, |
| {Model: "gpt-3.5-turbo"}, |
| }, |
| }, |
| APIKeys: []model.APIKey{ |
| {KeyIndex: 0, APIKey: "sk-import-key-1", KeyStrategy: model.KeyStrategySequential}, |
| {KeyIndex: 1, APIKey: "sk-import-key-2", KeyStrategy: model.KeyStrategySequential}, |
| }, |
| }, |
| { |
| Config: &model.Config{ |
| Name: "imported-channel-2", |
| URL: "https://api2.example.com", |
| Priority: 20, |
| Enabled: true, |
| ScheduledCheckEnabled: false, |
| ChannelType: "anthropic", |
| ModelEntries: []model.ModelEntry{ |
| {Model: "claude-3"}, |
| }, |
| }, |
| APIKeys: []model.APIKey{ |
| {KeyIndex: 0, APIKey: "sk-anthropic-key", KeyStrategy: model.KeyStrategyRoundRobin}, |
| }, |
| }, |
| } |
|
|
| created, updated, err := store.ImportChannelBatch(ctx, channels) |
| if err != nil { |
| t.Fatalf("import channel batch: %v", err) |
| } |
| if created != 2 { |
| t.Errorf("expected 2 created, got %d", created) |
| } |
| if updated != 0 { |
| t.Errorf("expected 0 updated, got %d", updated) |
| } |
|
|
| |
| configs, err := store.ListConfigs(ctx) |
| if err != nil { |
| t.Fatalf("list configs: %v", err) |
| } |
| if len(configs) != 2 { |
| t.Errorf("expected 2 channels, got %d", len(configs)) |
| } |
|
|
| configsByName := make(map[string]*model.Config, len(configs)) |
| for _, cfg := range configs { |
| configsByName[cfg.Name] = cfg |
| } |
| if !configsByName["imported-channel-1"].ScheduledCheckEnabled { |
| t.Fatalf("expected imported-channel-1 scheduled check enabled") |
| } |
| if configsByName["imported-channel-2"].ScheduledCheckEnabled { |
| t.Fatalf("expected imported-channel-2 scheduled check disabled") |
| } |
|
|
| |
| allKeys, err := store.GetAllAPIKeys(ctx) |
| if err != nil { |
| t.Fatalf("get all api keys: %v", err) |
| } |
| if len(allKeys) != 2 { |
| t.Fatalf("expected keys for 2 channels, got %d", len(allKeys)) |
| } |
| if countAPIKeys(allKeys) != 3 { |
| t.Fatalf("expected 3 api keys total, got %d", countAPIKeys(allKeys)) |
| } |
|
|
| idsByName := make(map[string]int64, len(configs)) |
| for _, cfg := range configs { |
| idsByName[cfg.Name] = cfg.ID |
| } |
| id1 := idsByName["imported-channel-1"] |
| id2 := idsByName["imported-channel-2"] |
| if id1 == 0 || id2 == 0 { |
| t.Fatalf("expected imported channels to have non-zero ids, got id1=%d id2=%d", id1, id2) |
| } |
|
|
| keys1, err := store.GetAPIKeys(ctx, id1) |
| if err != nil { |
| t.Fatalf("get api keys for imported-channel-1: %v", err) |
| } |
| if len(keys1) != 2 { |
| t.Fatalf("expected 2 keys for imported-channel-1, got %d", len(keys1)) |
| } |
| keys2, err := store.GetAPIKeys(ctx, id2) |
| if err != nil { |
| t.Fatalf("get api keys for imported-channel-2: %v", err) |
| } |
| if len(keys2) != 1 { |
| t.Fatalf("expected 1 key for imported-channel-2, got %d", len(keys2)) |
| } |
| } |
|
|
| func TestAPIKey_ImportChannelBatchPreservesScheduledCheckWithExplicitID(t *testing.T) { |
| t.Parallel() |
|
|
| store := newTestStore(t, "import-preserve-id.db") |
| ctx := context.Background() |
|
|
| channel := &model.ChannelWithKeys{ |
| Config: &model.Config{ |
| ID: 42, |
| Name: "imported-channel-explicit-id", |
| URL: "https://api.example.com", |
| Priority: 5, |
| Enabled: true, |
| ScheduledCheckEnabled: true, |
| ChannelType: "openai", |
| ModelEntries: []model.ModelEntry{ |
| {Model: "gpt-4o-mini"}, |
| }, |
| }, |
| APIKeys: []model.APIKey{ |
| {KeyIndex: 0, APIKey: "sk-import-key-explicit", KeyStrategy: model.KeyStrategySequential}, |
| }, |
| } |
|
|
| created, updated, err := store.ImportChannelBatch(ctx, []*model.ChannelWithKeys{channel}) |
| if err != nil { |
| t.Fatalf("import channel batch with explicit id: %v", err) |
| } |
| if created != 1 || updated != 0 { |
| t.Fatalf("expected created=1 updated=0, got created=%d updated=%d", created, updated) |
| } |
|
|
| config, err := store.GetConfig(ctx, channel.Config.ID) |
| if err != nil { |
| t.Fatalf("get imported config: %v", err) |
| } |
| if !config.ScheduledCheckEnabled { |
| t.Fatalf("expected scheduled_check_enabled to persist for explicit id import") |
| } |
| } |
|
|
| func TestAPIKey_ImportChannelBatchPreservesModelEntryOrder(t *testing.T) { |
| t.Parallel() |
|
|
| store := newTestStore(t, "import-preserve-model-order.db") |
| ctx := context.Background() |
|
|
| channel := &model.ChannelWithKeys{ |
| Config: &model.Config{ |
| Name: "imported-channel-order", |
| URL: "https://api.example.com", |
| Priority: 5, |
| Enabled: true, |
| ChannelType: "openai", |
| ModelEntries: []model.ModelEntry{ |
| {Model: "z-last"}, |
| {Model: "a-first"}, |
| }, |
| }, |
| APIKeys: []model.APIKey{ |
| {KeyIndex: 0, APIKey: "sk-import-key-order", KeyStrategy: model.KeyStrategySequential}, |
| }, |
| } |
|
|
| created, updated, err := store.ImportChannelBatch(ctx, []*model.ChannelWithKeys{channel}) |
| if err != nil { |
| t.Fatalf("import channel batch: %v", err) |
| } |
| if created != 1 || updated != 0 { |
| t.Fatalf("expected created=1 updated=0, got created=%d updated=%d", created, updated) |
| } |
|
|
| configs, err := store.ListConfigs(ctx) |
| if err != nil { |
| t.Fatalf("list configs: %v", err) |
| } |
| if len(configs) != 1 { |
| t.Fatalf("expected 1 config, got %d", len(configs)) |
| } |
|
|
| got := configs[0].ModelEntries |
| if len(got) != 2 { |
| t.Fatalf("expected 2 model entries, got %+v", got) |
| } |
| if got[0].Model != "z-last" || got[1].Model != "a-first" { |
| t.Fatalf("expected model order [z-last a-first], got %+v", got) |
| } |
| } |
|
|