From 6e6c669ea060cddc7e001135279314711a3109d8 Mon Sep 17 00:00:00 2001 From: zenfun Date: Sun, 21 Dec 2025 16:18:38 +0800 Subject: [PATCH] test(log): expand handler, config, and metrics coverage --- internal/api/admin_issue_key_test.go | 2 +- internal/api/log_handler_test.go | 33 ++++++++++-- internal/api/log_webhook_handler_test.go | 2 +- internal/api/master_tokens_handler_test.go | 2 +- internal/api/model_handler_test.go | 2 +- internal/api/provider_handler_test.go | 2 +- internal/api/stats_handler_test.go | 4 +- internal/config/config_test.go | 14 +++++ internal/service/logger_test.go | 62 ++++++++++++++++++++++ 9 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 internal/config/config_test.go create mode 100644 internal/service/logger_test.go diff --git a/internal/api/admin_issue_key_test.go b/internal/api/admin_issue_key_test.go index 2d0dfc9..92d4700 100644 --- a/internal/api/admin_issue_key_test.go +++ b/internal/api/admin_issue_key_test.go @@ -34,7 +34,7 @@ func TestAdmin_IssueChildKeyForMaster_IssuedByAdminAndSynced(t *testing.T) { syncService := service.NewSyncService(rdb) masterService := service.NewMasterService(db) - adminHandler := NewAdminHandler(db, masterService, syncService) + adminHandler := NewAdminHandler(db, db, masterService, syncService) m, _, err := masterService.CreateMaster("m1", "default", 5, 10) if err != nil { diff --git a/internal/api/log_handler_test.go b/internal/api/log_handler_test.go index d2b1fbd..79332af 100644 --- a/internal/api/log_handler_test.go +++ b/internal/api/log_handler_test.go @@ -51,7 +51,7 @@ func TestMaster_ListSelfLogs_FiltersByMaster(t *testing.T) { t.Fatalf("create log2: %v", err) } - mh := &MasterHandler{db: db} + mh := &MasterHandler{db: db, logDB: db} withMaster := func(next gin.HandlerFunc) gin.HandlerFunc { return func(c *gin.Context) { @@ -69,7 +69,7 @@ func TestMaster_ListSelfLogs_FiltersByMaster(t *testing.T) { if rr.Code != http.StatusOK { t.Fatalf("expected 200, got %d body=%s", rr.Code, rr.Body.String()) } - var resp ListLogsResponse + var resp ListMasterLogsResponse if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { t.Fatalf("unmarshal: %v", err) } @@ -79,6 +79,31 @@ func TestMaster_ListSelfLogs_FiltersByMaster(t *testing.T) { if resp.Items[0].KeyID != k1.ID { t.Fatalf("expected key_id %d, got %+v", k1.ID, resp.Items[0]) } + + var raw map[string]any + if err := json.Unmarshal(rr.Body.Bytes(), &raw); err != nil { + t.Fatalf("unmarshal raw: %v", err) + } + items, ok := raw["items"].([]any) + if !ok || len(items) == 0 { + t.Fatalf("expected items array in response") + } + first, ok := items[0].(map[string]any) + if !ok { + t.Fatalf("expected item object in response") + } + if _, ok := first["provider_id"]; ok { + t.Fatalf("expected provider_id to be omitted") + } + if _, ok := first["provider_type"]; ok { + t.Fatalf("expected provider_type to be omitted") + } + if _, ok := first["provider_name"]; ok { + t.Fatalf("expected provider_name to be omitted") + } + if _, ok := first["client_ip"]; ok { + t.Fatalf("expected client_ip to be omitted") + } } func TestAdmin_DeleteLogs_BeforeFilters(t *testing.T) { @@ -114,7 +139,7 @@ func TestAdmin_DeleteLogs_BeforeFilters(t *testing.T) { t.Fatalf("create log3: %v", err) } - h := &Handler{db: db} + h := &Handler{db: db, logDB: db} r := gin.New() r.DELETE("/admin/logs", h.DeleteLogs) @@ -155,7 +180,7 @@ func TestAdmin_DeleteLogs_RequiresBefore(t *testing.T) { t.Fatalf("migrate: %v", err) } - h := &Handler{db: db} + h := &Handler{db: db, logDB: db} r := gin.New() r.DELETE("/admin/logs", h.DeleteLogs) diff --git a/internal/api/log_webhook_handler_test.go b/internal/api/log_webhook_handler_test.go index 8702b18..fec97d4 100644 --- a/internal/api/log_webhook_handler_test.go +++ b/internal/api/log_webhook_handler_test.go @@ -33,7 +33,7 @@ func newTestHandlerWithWebhook(t *testing.T) (*Handler, *miniredis.Miniredis) { mr := miniredis.RunT(t) rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) sync := service.NewSyncService(rdb) - return NewHandler(db, sync, nil, rdb), mr + return NewHandler(db, db, sync, nil, rdb), mr } func TestLogWebhookConfigCRUD(t *testing.T) { diff --git a/internal/api/master_tokens_handler_test.go b/internal/api/master_tokens_handler_test.go index a8e6b78..624763b 100644 --- a/internal/api/master_tokens_handler_test.go +++ b/internal/api/master_tokens_handler_test.go @@ -42,7 +42,7 @@ func TestMaster_ListTokens_AndUpdateToken(t *testing.T) { rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) syncSvc := service.NewSyncService(rdb) masterSvc := service.NewMasterService(db) - h := NewMasterHandler(db, masterSvc, syncSvc) + h := NewMasterHandler(db, db, masterSvc, syncSvc) withMaster := func(next gin.HandlerFunc) gin.HandlerFunc { return func(c *gin.Context) { diff --git a/internal/api/model_handler_test.go b/internal/api/model_handler_test.go index 69a23c3..aab5bcb 100644 --- a/internal/api/model_handler_test.go +++ b/internal/api/model_handler_test.go @@ -33,7 +33,7 @@ func newTestHandlerWithRedis(t *testing.T) (*Handler, *gorm.DB, *miniredis.Minir mr := miniredis.RunT(t) rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) sync := service.NewSyncService(rdb) - return NewHandler(db, sync, nil, rdb), db, mr + return NewHandler(db, db, sync, nil, rdb), db, mr } func TestCreateModel_DefaultsKindChat_AndWritesModelsMeta(t *testing.T) { diff --git a/internal/api/provider_handler_test.go b/internal/api/provider_handler_test.go index 0999228..08a7da2 100644 --- a/internal/api/provider_handler_test.go +++ b/internal/api/provider_handler_test.go @@ -36,7 +36,7 @@ func newTestHandler(t *testing.T) (*Handler, *gorm.DB) { rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) sync := service.NewSyncService(rdb) - return NewHandler(db, sync, nil, rdb), db + return NewHandler(db, db, sync, nil, rdb), db } func TestCreateProvider_DefaultsVertexLocationGlobal(t *testing.T) { diff --git a/internal/api/stats_handler_test.go b/internal/api/stats_handler_test.go index 02fa1eb..1c7d78f 100644 --- a/internal/api/stats_handler_test.go +++ b/internal/api/stats_handler_test.go @@ -72,7 +72,7 @@ func TestMasterStats_AggregatesByKeyAndModel(t *testing.T) { rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) masterSvc := service.NewMasterService(db) syncSvc := service.NewSyncService(rdb) - h := NewMasterHandler(db, masterSvc, syncSvc) + h := NewMasterHandler(db, db, masterSvc, syncSvc) withMaster := func(next gin.HandlerFunc) gin.HandlerFunc { return func(c *gin.Context) { @@ -163,7 +163,7 @@ func TestAdminStats_AggregatesByProvider(t *testing.T) { rdb := redis.NewClient(&redis.Options{Addr: mr.Addr()}) masterSvc := service.NewMasterService(db) syncSvc := service.NewSyncService(rdb) - adminHandler := NewAdminHandler(db, masterSvc, syncSvc) + adminHandler := NewAdminHandler(db, db, masterSvc, syncSvc) r := gin.New() r.GET("/admin/stats", adminHandler.GetAdminStats) diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..5b4386f --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,14 @@ +package config + +import "testing" + +func TestLoad_LogDSNOverride(t *testing.T) { + t.Setenv("EZ_LOG_PG_DSN", "host=log-db user=postgres dbname=logs") + cfg, err := Load() + if err != nil { + t.Fatalf("load config: %v", err) + } + if cfg.Log.DSN != "host=log-db user=postgres dbname=logs" { + t.Fatalf("expected log dsn to be set, got %q", cfg.Log.DSN) + } +} diff --git a/internal/service/logger_test.go b/internal/service/logger_test.go new file mode 100644 index 0000000..bd42ce5 --- /dev/null +++ b/internal/service/logger_test.go @@ -0,0 +1,62 @@ +package service + +import ( + "context" + "expvar" + "fmt" + "strconv" + "testing" + "time" + + "github.com/ez-api/ez-api/internal/model" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestLogWriterMetrics(t *testing.T) { + dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name()) + db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{}) + if err != nil { + t.Fatalf("open sqlite: %v", err) + } + if err := db.AutoMigrate(&model.LogRecord{}); err != nil { + t.Fatalf("migrate: %v", err) + } + + startBatch := getExpvarInt(t, "log_write_batch_total") + startDropped := getExpvarInt(t, "log_queue_dropped_total") + + dropWriter := NewLogWriter(db, 1, 10, time.Second) + dropWriter.Write(model.LogRecord{ModelName: "m1", StatusCode: 200}) + dropWriter.Write(model.LogRecord{ModelName: "m2", StatusCode: 200}) + + writer := NewLogWriter(db, 10, 1, 10*time.Millisecond) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + writer.Start(ctx) + writer.Write(model.LogRecord{ModelName: "m3", StatusCode: 200}) + + time.Sleep(50 * time.Millisecond) + cancel() + + if got := getExpvarInt(t, "log_write_batch_total"); got <= startBatch { + t.Fatalf("expected batch counter to increase, start=%d got=%d", startBatch, got) + } + if got := getExpvarInt(t, "log_queue_dropped_total"); got <= startDropped { + t.Fatalf("expected dropped counter to be >= start, start=%d got=%d", startDropped, got) + } +} + +func getExpvarInt(t *testing.T, name string) int64 { + t.Helper() + v := expvar.Get(name) + if v == nil { + t.Fatalf("expvar %s not found", name) + } + raw := v.String() + n, err := strconv.ParseInt(raw, 10, 64) + if err != nil { + t.Fatalf("parse expvar %s: %v", name, err) + } + return n +}