| package sql_test |
|
|
| import ( |
| "context" |
| "path/filepath" |
| "testing" |
| "time" |
|
|
| "ccLoad/internal/storage" |
| ) |
|
|
| func TestCooldown_ChannelCooldown(t *testing.T) { |
| t.Parallel() |
|
|
| tmp := t.TempDir() |
| store, err := storage.CreateSQLiteStore(filepath.Join(tmp, "cooldown.db")) |
| if err != nil { |
| t.Fatalf("create sqlite store: %v", err) |
| } |
| t.Cleanup(func() { _ = store.Close() }) |
|
|
| ctx := context.Background() |
| channelID := createTestChannel(t, ctx, store, "test-channel-cooldown") |
|
|
| |
| cooldowns, err := store.GetAllChannelCooldowns(ctx) |
| if err != nil { |
| t.Fatalf("get all channel cooldowns: %v", err) |
| } |
| if _, exists := cooldowns[channelID]; exists { |
| t.Error("expected no cooldown initially") |
| } |
|
|
| |
| now := time.Now() |
| duration, err := store.BumpChannelCooldown(ctx, channelID, now, 500) |
| if err != nil { |
| t.Fatalf("bump channel cooldown: %v", err) |
| } |
| if duration < time.Second { |
| t.Errorf("expected duration >= 1s, got %v", duration) |
| } |
|
|
| |
| cooldowns, err = store.GetAllChannelCooldowns(ctx) |
| if err != nil { |
| t.Fatalf("get all channel cooldowns: %v", err) |
| } |
| until, exists := cooldowns[channelID] |
| if !exists { |
| t.Error("expected cooldown to be set") |
| } |
| if until.Before(now) { |
| t.Errorf("cooldown until should be in future: got %v, now %v", until, now) |
| } |
|
|
| |
| duration2, err := store.BumpChannelCooldown(ctx, channelID, now, 500) |
| if err != nil { |
| t.Fatalf("bump channel cooldown second time: %v", err) |
| } |
| if duration2 <= duration { |
| t.Errorf("expected exponential backoff: first=%v, second=%v", duration, duration2) |
| } |
|
|
| |
| futureTime := time.Now().Add(10 * time.Minute) |
| if err := store.SetChannelCooldown(ctx, channelID, futureTime); err != nil { |
| t.Fatalf("set channel cooldown: %v", err) |
| } |
| cooldowns, err = store.GetAllChannelCooldowns(ctx) |
| if err != nil { |
| t.Fatalf("get all channel cooldowns after set: %v", err) |
| } |
| |
| if cooldowns[channelID].Sub(futureTime).Abs() > time.Second { |
| t.Errorf("expected cooldown until ~%v, got %v", futureTime, cooldowns[channelID]) |
| } |
|
|
| |
| if err := store.ResetChannelCooldown(ctx, channelID); err != nil { |
| t.Fatalf("reset channel cooldown: %v", err) |
| } |
| cooldowns, err = store.GetAllChannelCooldowns(ctx) |
| if err != nil { |
| t.Fatalf("get all channel cooldowns after reset: %v", err) |
| } |
| if _, exists := cooldowns[channelID]; exists { |
| t.Error("expected cooldown to be cleared after reset") |
| } |
| } |
|
|
| func TestCooldown_KeyCooldown(t *testing.T) { |
| t.Parallel() |
|
|
| tmp := t.TempDir() |
| store, err := storage.CreateSQLiteStore(filepath.Join(tmp, "key_cooldown.db")) |
| if err != nil { |
| t.Fatalf("create sqlite store: %v", err) |
| } |
| t.Cleanup(func() { _ = store.Close() }) |
|
|
| ctx := context.Background() |
| channelID := createTestChannel(t, ctx, store, "test-key-cooldown") |
| createTestAPIKey(t, ctx, store, channelID, 0) |
|
|
| |
| allKeyCooldowns, err := store.GetAllKeyCooldowns(ctx) |
| if err != nil { |
| t.Fatalf("get all key cooldowns: %v", err) |
| } |
| if len(allKeyCooldowns) > 0 { |
| t.Errorf("expected no key cooldowns initially, got %d", len(allKeyCooldowns)) |
| } |
|
|
| |
| now := time.Now() |
| duration, err := store.BumpKeyCooldown(ctx, channelID, 0, now, 429) |
| if err != nil { |
| t.Fatalf("bump key cooldown: %v", err) |
| } |
| if duration < time.Second { |
| t.Errorf("expected duration >= 1s, got %v", duration) |
| } |
|
|
| |
| allKeyCooldowns, err = store.GetAllKeyCooldowns(ctx) |
| if err != nil { |
| t.Fatalf("get all key cooldowns after bump: %v", err) |
| } |
| if allKeyCooldowns[channelID] == nil { |
| t.Error("expected channel in cooldowns map") |
| } else if until, exists := allKeyCooldowns[channelID][0]; !exists { |
| t.Error("expected key 0 cooldown to be set") |
| } else if until.Before(now) { |
| t.Errorf("cooldown until should be in future: got %v, now %v", until, now) |
| } |
|
|
| |
| duration2, err := store.BumpKeyCooldown(ctx, channelID, 0, now, 429) |
| if err != nil { |
| t.Fatalf("bump key cooldown second time: %v", err) |
| } |
| if duration2 <= duration { |
| t.Errorf("expected exponential backoff: first=%v, second=%v", duration, duration2) |
| } |
|
|
| |
| futureTime := time.Now().Add(5 * time.Minute) |
| if err := store.SetKeyCooldown(ctx, channelID, 0, futureTime); err != nil { |
| t.Fatalf("set key cooldown: %v", err) |
| } |
| allKeyCooldowns, err = store.GetAllKeyCooldowns(ctx) |
| if err != nil { |
| t.Fatalf("get all key cooldowns after set: %v", err) |
| } |
| if until, exists := allKeyCooldowns[channelID][0]; !exists { |
| t.Error("expected cooldown after set") |
| } else if until.Sub(futureTime).Abs() > time.Second { |
| t.Errorf("expected cooldown until ~%v, got %v", futureTime, until) |
| } |
|
|
| |
| if err := store.ResetKeyCooldown(ctx, channelID, 0); err != nil { |
| t.Fatalf("reset key cooldown: %v", err) |
| } |
| allKeyCooldowns, err = store.GetAllKeyCooldowns(ctx) |
| if err != nil { |
| t.Fatalf("get all key cooldowns after reset: %v", err) |
| } |
| if len(allKeyCooldowns[channelID]) > 0 { |
| t.Error("expected cooldown to be cleared after reset") |
| } |
| } |
|
|
| func TestCooldown_BumpChannelCooldown_NotFound(t *testing.T) { |
| t.Parallel() |
|
|
| tmp := t.TempDir() |
| store, err := storage.CreateSQLiteStore(filepath.Join(tmp, "notfound.db")) |
| if err != nil { |
| t.Fatalf("create sqlite store: %v", err) |
| } |
| t.Cleanup(func() { _ = store.Close() }) |
|
|
| ctx := context.Background() |
|
|
| |
| _, err = store.BumpChannelCooldown(ctx, 99999, time.Now(), 500) |
| if err == nil { |
| t.Error("expected error for non-existent channel") |
| } |
| } |
|
|
| func TestCooldown_BumpKeyCooldown_NotFound(t *testing.T) { |
| t.Parallel() |
|
|
| tmp := t.TempDir() |
| store, err := storage.CreateSQLiteStore(filepath.Join(tmp, "key_notfound.db")) |
| if err != nil { |
| t.Fatalf("create sqlite store: %v", err) |
| } |
| t.Cleanup(func() { _ = store.Close() }) |
|
|
| ctx := context.Background() |
|
|
| |
| _, err = store.BumpKeyCooldown(ctx, 99999, 0, time.Now(), 429) |
| if err == nil { |
| t.Error("expected error for non-existent key") |
| } |
| } |
|
|
| func TestCooldown_AuthErrorBackoff(t *testing.T) { |
| t.Parallel() |
|
|
| tmp := t.TempDir() |
| store, err := storage.CreateSQLiteStore(filepath.Join(tmp, "auth_backoff.db")) |
| if err != nil { |
| t.Fatalf("create sqlite store: %v", err) |
| } |
| t.Cleanup(func() { _ = store.Close() }) |
|
|
| ctx := context.Background() |
| channelID := createTestChannel(t, ctx, store, "test-auth-backoff") |
| createTestAPIKey(t, ctx, store, channelID, 0) |
|
|
| |
| now := time.Now() |
| duration, err := store.BumpKeyCooldown(ctx, channelID, 0, now, 401) |
| if err != nil { |
| t.Fatalf("bump key cooldown with 401: %v", err) |
| } |
|
|
| |
| if duration < 5*time.Minute { |
| t.Errorf("expected auth error to have longer cooldown (>=5m), got %v", duration) |
| } |
| } |
|
|