| package storage |
|
|
| import ( |
| "context" |
| "errors" |
| "testing" |
| "time" |
|
|
| "ccLoad/internal/model" |
| sqlstore "ccLoad/internal/storage/sql" |
| ) |
|
|
| |
| func createTestStoreForSync(t *testing.T, suffix string) *sqlstore.SQLStore { |
| t.Helper() |
| tmpDB := t.TempDir() + "/sync_" + suffix + ".db" |
| store, err := CreateSQLiteStore(tmpDB) |
| if err != nil { |
| t.Fatalf("创建测试存储失败: %v", err) |
| } |
| return store.(*sqlstore.SQLStore) |
| } |
|
|
| func TestSyncManager_RestoreOnStartup_EmptyMySQL(t *testing.T) { |
| |
| mysql := createTestStoreForSync(t, "mysql_empty") |
| sqlite := createTestStoreForSync(t, "sqlite_empty") |
| defer func() { |
| _ = mysql.Close() |
| _ = sqlite.Close() |
| }() |
|
|
| sm := NewSyncManager(mysql, sqlite) |
|
|
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
| defer cancel() |
|
|
| |
| err := sm.RestoreOnStartup(ctx, 7) |
| if err != nil { |
| t.Fatalf("RestoreOnStartup 失败: %v", err) |
| } |
| } |
|
|
| func TestSyncManager_RestoreOnStartup_WithData(t *testing.T) { |
| |
| mysql := createTestStoreForSync(t, "mysql_data") |
| sqlite := createTestStoreForSync(t, "sqlite_data") |
| defer func() { |
| _ = mysql.Close() |
| _ = sqlite.Close() |
| }() |
|
|
| ctx := context.Background() |
|
|
| |
| cfg := &model.Config{ |
| Name: "test-channel", |
| ChannelType: "openai", |
| URL: "https://api.openai.com", |
| Priority: 100, |
| Enabled: true, |
| } |
| created, err := mysql.CreateConfig(ctx, cfg) |
| if err != nil { |
| t.Fatalf("创建测试数据失败: %v", err) |
| } |
|
|
| |
| _, err = sqlite.GetConfig(ctx, created.ID) |
| if err == nil { |
| t.Fatal("SQLite 中不应该有数据") |
| } |
|
|
| |
| sm := NewSyncManager(mysql, sqlite) |
| restoreCtx, cancel := context.WithTimeout(ctx, 30*time.Second) |
| defer cancel() |
|
|
| err = sm.RestoreOnStartup(restoreCtx, 0) |
| if err != nil { |
| t.Fatalf("RestoreOnStartup 失败: %v", err) |
| } |
|
|
| |
| restored, err := sqlite.GetConfig(ctx, created.ID) |
| if err != nil { |
| t.Fatalf("恢复后获取配置失败: %v", err) |
| } |
| if restored.Name != cfg.Name { |
| t.Errorf("恢复的配置名称不匹配: got %s, want %s", restored.Name, cfg.Name) |
| } |
| } |
|
|
| func TestSyncManager_RestoreLogsIncremental(t *testing.T) { |
| mysql := createTestStoreForSync(t, "mysql_logs") |
| sqlite := createTestStoreForSync(t, "sqlite_logs") |
| defer func() { |
| _ = mysql.Close() |
| _ = sqlite.Close() |
| }() |
|
|
| ctx := context.Background() |
|
|
| |
| now := time.Now() |
| for i := 0; i < 5; i++ { |
| entry := &model.LogEntry{ |
| Time: model.JSONTime{Time: now.Add(-time.Duration(i) * time.Hour)}, |
| ChannelID: 1, |
| Model: "gpt-4", |
| StatusCode: 200, |
| Duration: 1.5, |
| } |
| if err := mysql.AddLog(ctx, entry); err != nil { |
| t.Fatalf("添加日志失败: %v", err) |
| } |
| } |
|
|
| |
| mysqlLogs, err := mysql.ListLogs(ctx, now.Add(-24*time.Hour), 100, 0, nil) |
| if err != nil { |
| t.Fatalf("查询 MySQL 日志失败: %v", err) |
| } |
| if len(mysqlLogs) != 5 { |
| t.Fatalf("MySQL 日志数量不匹配: got %d, want 5", len(mysqlLogs)) |
| } |
|
|
| |
| sm := NewSyncManager(mysql, sqlite) |
| restoreCtx, cancel := context.WithTimeout(ctx, 30*time.Second) |
| defer cancel() |
|
|
| err = sm.RestoreOnStartup(restoreCtx, 7) |
| if err != nil { |
| t.Fatalf("RestoreOnStartup 失败: %v", err) |
| } |
|
|
| |
| sqliteLogs, err := sqlite.ListLogs(ctx, now.Add(-24*time.Hour), 100, 0, nil) |
| if err != nil { |
| t.Fatalf("查询 SQLite 日志失败: %v", err) |
| } |
| if len(sqliteLogs) != 5 { |
| t.Errorf("SQLite 日志数量不匹配: got %d, want 5", len(sqliteLogs)) |
| } |
| } |
|
|
| func TestSyncManager_RestoreLogsIncremental_ZeroDays(t *testing.T) { |
| mysql := createTestStoreForSync(t, "mysql_nologs") |
| sqlite := createTestStoreForSync(t, "sqlite_nologs") |
| defer func() { |
| _ = mysql.Close() |
| _ = sqlite.Close() |
| }() |
|
|
| ctx := context.Background() |
|
|
| |
| entry := &model.LogEntry{ |
| Time: model.JSONTime{Time: time.Now()}, |
| ChannelID: 1, |
| Model: "gpt-4", |
| StatusCode: 200, |
| Duration: 1.5, |
| } |
| if err := mysql.AddLog(ctx, entry); err != nil { |
| t.Fatalf("添加日志失败: %v", err) |
| } |
|
|
| |
| sm := NewSyncManager(mysql, sqlite) |
| restoreCtx, cancel := context.WithTimeout(ctx, 30*time.Second) |
| defer cancel() |
|
|
| err := sm.RestoreOnStartup(restoreCtx, 0) |
| if err != nil { |
| t.Fatalf("RestoreOnStartup 失败: %v", err) |
| } |
|
|
| |
| sqliteLogs, err := sqlite.ListLogs(ctx, time.Now().Add(-24*time.Hour), 100, 0, nil) |
| if err != nil { |
| t.Fatalf("查询 SQLite 日志失败: %v", err) |
| } |
| if len(sqliteLogs) != 0 { |
| t.Errorf("SQLite 不应该有日志(logDays=0),got %d", len(sqliteLogs)) |
| } |
| } |
|
|
| |
| |
| func TestSyncManager_RestoreLogsIncremental_TrueIncremental(t *testing.T) { |
| mysql := createTestStoreForSync(t, "mysql_incr") |
| sqlite := createTestStoreForSync(t, "sqlite_incr") |
| defer func() { |
| _ = mysql.Close() |
| _ = sqlite.Close() |
| }() |
|
|
| ctx := context.Background() |
| now := time.Now() |
|
|
| |
| for i := 0; i < 3; i++ { |
| entry := &model.LogEntry{ |
| Time: model.JSONTime{Time: now.Add(-time.Duration(i) * time.Hour)}, |
| ChannelID: 1, |
| Model: "gpt-4", |
| StatusCode: 200, |
| Duration: 1.5, |
| } |
| if err := mysql.AddLog(ctx, entry); err != nil { |
| t.Fatalf("添加日志失败: %v", err) |
| } |
| } |
|
|
| |
| sm := NewSyncManager(mysql, sqlite) |
| if err := sm.RestoreOnStartup(ctx, 7); err != nil { |
| t.Fatalf("第一次 RestoreOnStartup 失败: %v", err) |
| } |
|
|
| |
| sqliteLogs, err := sqlite.ListLogs(ctx, now.Add(-24*time.Hour), 100, 0, nil) |
| if err != nil { |
| t.Fatalf("查询 SQLite 日志失败: %v", err) |
| } |
| if len(sqliteLogs) != 3 { |
| t.Fatalf("第一次恢复后 SQLite 日志数量不匹配: got %d, want 3", len(sqliteLogs)) |
| } |
|
|
| |
| for i := 0; i < 2; i++ { |
| entry := &model.LogEntry{ |
| Time: model.JSONTime{Time: now.Add(time.Duration(i+1) * time.Minute)}, |
| ChannelID: 2, |
| Model: "gpt-3.5", |
| StatusCode: 200, |
| Duration: 0.5, |
| } |
| if err := mysql.AddLog(ctx, entry); err != nil { |
| t.Fatalf("添加新日志失败: %v", err) |
| } |
| } |
|
|
| |
| sm2 := NewSyncManager(mysql, sqlite) |
| if err := sm2.RestoreOnStartup(ctx, 7); err != nil { |
| t.Fatalf("第二次 RestoreOnStartup 失败: %v", err) |
| } |
|
|
| |
| sqliteLogs, err = sqlite.ListLogs(ctx, now.Add(-24*time.Hour), 100, 0, nil) |
| if err != nil { |
| t.Fatalf("查询 SQLite 日志失败: %v", err) |
| } |
| if len(sqliteLogs) != 5 { |
| t.Fatalf("第二次恢复后 SQLite 日志数量不匹配: got %d, want 5", len(sqliteLogs)) |
| } |
|
|
| |
| count1 := 0 |
| count2 := 0 |
| for _, entry := range sqliteLogs { |
| switch entry.ChannelID { |
| case 1: |
| count1++ |
| case 2: |
| count2++ |
| } |
| } |
| if count1 != 3 { |
| t.Errorf("原有日志(channel_id=1)被意外修改: got %d, want 3", count1) |
| } |
| if count2 != 2 { |
| t.Errorf("新增日志(channel_id=2)数量不对: got %d, want 2", count2) |
| } |
| } |
|
|
| type fakeRowsErrAfterOne struct { |
| scanned bool |
| err error |
| } |
|
|
| func (r *fakeRowsErrAfterOne) Next() bool { |
| if r.scanned { |
| return false |
| } |
| r.scanned = true |
| return true |
| } |
|
|
| func (r *fakeRowsErrAfterOne) Scan(dest ...any) error { |
| for i := range dest { |
| *(dest[i].(*any)) = int64(i + 1) |
| } |
| return nil |
| } |
|
|
| func (r *fakeRowsErrAfterOne) Err() error { return r.err } |
|
|
| func TestSyncManager_InsertLogBatch_ChecksRowsErr(t *testing.T) { |
| mysql := createTestStoreForSync(t, "mysql_data") |
| sqlite := createTestStoreForSync(t, "sqlite_data") |
| defer func() { |
| _ = mysql.Close() |
| _ = sqlite.Close() |
| }() |
|
|
| sm := NewSyncManager(mysql, sqlite) |
|
|
| _, _, err := sm.insertLogBatchWithLastID( |
| context.Background(), |
| &fakeRowsErrAfterOne{err: errors.New("driver error")}, |
| 2, |
| []string{"id"}, |
| []int{0}, |
| ) |
| if err == nil { |
| t.Fatalf("期望返回错误,但得到 nil") |
| } |
| } |
|
|