package sql_test import ( "context" "testing" "time" "ccLoad/internal/model" ) func newJSONTime(t time.Time) model.JSONTime { return model.JSONTime{Time: t} } func TestLog_AddAndList(t *testing.T) { t.Parallel() store := newTestStore(t, "logs.db") ctx := context.Background() channelID := createTestChannel(t, ctx, store, "log-test-channel") now := time.Now() log := &model.LogEntry{ Time: newJSONTime(now), Model: "gpt-4", ChannelID: channelID, StatusCode: 200, Message: "success", Duration: 1.5, IsStreaming: false, APIKeyUsed: "abcd...efgh", } if err := store.AddLog(ctx, log); err != nil { t.Fatalf("add log: %v", err) } // AddLog 方法不返回 ID,不需要检查 since := now.Add(-1 * time.Hour) logs, err := store.ListLogs(ctx, since, 10, 0, nil) if err != nil { t.Fatalf("list logs: %v", err) } if len(logs) != 1 { t.Errorf("expected 1 log, got %d", len(logs)) } if len(logs) > 0 && logs[0].Model != "gpt-4" { t.Errorf("model: got %q, want %q", logs[0].Model, "gpt-4") } } func TestLog_BatchAdd(t *testing.T) { t.Parallel() store := newTestStore(t, "batch_logs.db") ctx := context.Background() channelID := createTestChannel(t, ctx, store, "batch-log-channel") now := time.Now() logs := []*model.LogEntry{ { Time: newJSONTime(now), Model: "gpt-4", ChannelID: channelID, StatusCode: 200, Message: "success 1", Duration: 1.0, APIKeyUsed: "key1...1key", }, { Time: newJSONTime(now), Model: "claude-3", ChannelID: channelID, StatusCode: 200, Message: "success 2", Duration: 2.0, APIKeyUsed: "key2...2key", }, { Time: newJSONTime(now), Model: "gpt-4", ChannelID: channelID, StatusCode: 500, Message: "error", Duration: 0.5, APIKeyUsed: "key3...3key", }, } if err := store.BatchAddLogs(ctx, logs); err != nil { t.Fatalf("batch add logs: %v", err) } // BatchAddLogs 方法不返回 ID,不需要检查 since := now.Add(-1 * time.Hour) count, err := store.CountLogs(ctx, since, nil) if err != nil { t.Fatalf("count logs: %v", err) } if count != 3 { t.Errorf("expected 3 logs, got %d", count) } } func TestLog_ListRange(t *testing.T) { t.Parallel() store := newTestStore(t, "range_logs.db") ctx := context.Background() channelID := createTestChannel(t, ctx, store, "range-log-channel") now := time.Now() logs := []*model.LogEntry{ { Time: newJSONTime(now.Add(-2 * time.Hour)), Model: "old-model", ChannelID: channelID, StatusCode: 200, Message: "old log", Duration: 1.0, APIKeyUsed: "key1...1key", }, { Time: newJSONTime(now.Add(-30 * time.Minute)), Model: "recent-model", ChannelID: channelID, StatusCode: 200, Message: "recent log", Duration: 1.0, APIKeyUsed: "key2...2key", }, } if err := store.BatchAddLogs(ctx, logs); err != nil { t.Fatalf("batch add logs: %v", err) } startTime := now.Add(-1 * time.Hour) endTime := now rangeLogs, err := store.ListLogsRange(ctx, startTime, endTime, 100, 0, nil) if err != nil { t.Fatalf("list logs range: %v", err) } if len(rangeLogs) != 1 { t.Errorf("expected 1 log in range, got %d", len(rangeLogs)) } if len(rangeLogs) > 0 && rangeLogs[0].Model != "recent-model" { t.Errorf("model: got %q, want %q", rangeLogs[0].Model, "recent-model") } rangeCount, err := store.CountLogsRange(ctx, startTime, endTime, nil) if err != nil { t.Fatalf("count logs range: %v", err) } if rangeCount != 1 { t.Errorf("expected 1 log in range count, got %d", rangeCount) } } func TestLog_Pagination(t *testing.T) { t.Parallel() store := newTestStore(t, "pagination_logs.db") ctx := context.Background() channelID := createTestChannel(t, ctx, store, "pagination-channel") now := time.Now() logs := make([]*model.LogEntry, 10) for i := 0; i < 10; i++ { logs[i] = &model.LogEntry{ Time: newJSONTime(now), Model: "gpt-4", ChannelID: channelID, StatusCode: 200, Message: "log " + string(rune('0'+i)), Duration: float64(i), APIKeyUsed: "key...key", } } if err := store.BatchAddLogs(ctx, logs); err != nil { t.Fatalf("batch add logs: %v", err) } since := now.Add(-1 * time.Hour) page1, err := store.ListLogs(ctx, since, 5, 0, nil) if err != nil { t.Fatalf("list logs page 1: %v", err) } if len(page1) != 5 { t.Errorf("page 1: expected 5 logs, got %d", len(page1)) } page2, err := store.ListLogs(ctx, since, 5, 5, nil) if err != nil { t.Fatalf("list logs page 2: %v", err) } if len(page2) != 5 { t.Errorf("page 2: expected 5 logs, got %d", len(page2)) } seen := make(map[int64]struct{}, len(page1)) for _, entry := range page1 { seen[entry.ID] = struct{}{} } for _, entry := range page2 { if _, ok := seen[entry.ID]; ok { t.Fatalf("pages should not overlap, overlapping id=%d", entry.ID) } } } func TestLog_ListRangeWithCount_PreservesZeroCostMultiplier(t *testing.T) { t.Parallel() store := newTestStore(t, "logs_zero_multiplier.db") ctx := context.Background() channelID := createTestChannel(t, ctx, store, "free-log-channel") now := time.Now() if err := store.AddLog(ctx, &model.LogEntry{ Time: newJSONTime(now), Model: "gpt-5.4-mini", ChannelID: channelID, StatusCode: 200, Message: "success", Duration: 1.2, APIKeyUsed: "key...key", Cost: 0.019, CostMultiplier: 0, }); err != nil { t.Fatalf("add log: %v", err) } startTime := now.Add(-1 * time.Minute) endTime := now.Add(1 * time.Minute) logs, total, err := store.ListLogsRangeWithCount(ctx, startTime, endTime, 10, 0, nil) if err != nil { t.Fatalf("ListLogsRangeWithCount failed: %v", err) } if total != 1 { t.Fatalf("total=%d, want 1", total) } if len(logs) != 1 { t.Fatalf("len(logs)=%d, want 1", len(logs)) } if logs[0].CostMultiplier != 0 { t.Fatalf("cost_multiplier=%v, want 0", logs[0].CostMultiplier) } }