diff --git a/backend/internal/repository/ops_repo_metrics.go b/backend/internal/repository/ops_repo_metrics.go index f1e57c3864..55eda23ed9 100644 --- a/backend/internal/repository/ops_repo_metrics.go +++ b/backend/internal/repository/ops_repo_metrics.go @@ -114,37 +114,37 @@ INSERT INTO ops_system_metrics ( opsNullFloat64(input.QPS), opsNullFloat64(input.TPS), - opsNullInt(input.DurationP50Ms), - opsNullInt(input.DurationP90Ms), - opsNullInt(input.DurationP95Ms), - opsNullInt(input.DurationP99Ms), + opsNullMetricInt(input.DurationP50Ms), + opsNullMetricInt(input.DurationP90Ms), + opsNullMetricInt(input.DurationP95Ms), + opsNullMetricInt(input.DurationP99Ms), opsNullFloat64(input.DurationAvgMs), - opsNullInt(input.DurationMaxMs), + opsNullMetricInt(input.DurationMaxMs), - opsNullInt(input.TTFTP50Ms), - opsNullInt(input.TTFTP90Ms), - opsNullInt(input.TTFTP95Ms), - opsNullInt(input.TTFTP99Ms), + opsNullMetricInt(input.TTFTP50Ms), + opsNullMetricInt(input.TTFTP90Ms), + opsNullMetricInt(input.TTFTP95Ms), + opsNullMetricInt(input.TTFTP99Ms), opsNullFloat64(input.TTFTAvgMs), - opsNullInt(input.TTFTMaxMs), + opsNullMetricInt(input.TTFTMaxMs), opsNullFloat64(input.CPUUsagePercent), - opsNullInt(input.MemoryUsedMB), - opsNullInt(input.MemoryTotalMB), + opsNullMetricInt64(input.MemoryUsedMB), + opsNullMetricInt64(input.MemoryTotalMB), opsNullFloat64(input.MemoryUsagePercent), opsNullBool(input.DBOK), opsNullBool(input.RedisOK), - opsNullInt(input.RedisConnTotal), - opsNullInt(input.RedisConnIdle), + opsNullMetricInt(input.RedisConnTotal), + opsNullMetricInt(input.RedisConnIdle), - opsNullInt(input.DBConnActive), - opsNullInt(input.DBConnIdle), - opsNullInt(input.DBConnWaiting), + opsNullMetricInt(input.DBConnActive), + opsNullMetricInt(input.DBConnIdle), + opsNullMetricInt(input.DBConnWaiting), - opsNullInt(input.GoroutineCount), - opsNullInt(input.ConcurrencyQueueDepth), + opsNullMetricInt(input.GoroutineCount), + opsNullMetricInt(input.ConcurrencyQueueDepth), ) return err } @@ -437,6 +437,20 @@ func opsNullFloat64(v *float64) any { return sql.NullFloat64{Float64: *v, Valid: true} } +func opsNullMetricInt(v *int) any { + if v == nil { + return sql.NullInt64{} + } + return sql.NullInt64{Int64: int64(*v), Valid: true} +} + +func opsNullMetricInt64(v *int64) any { + if v == nil { + return sql.NullInt64{} + } + return sql.NullInt64{Int64: *v, Valid: true} +} + func opsNullTime(v *time.Time) any { if v == nil || v.IsZero() { return sql.NullTime{} diff --git a/backend/internal/repository/ops_repo_metrics_test.go b/backend/internal/repository/ops_repo_metrics_test.go new file mode 100644 index 0000000000..af41069656 --- /dev/null +++ b/backend/internal/repository/ops_repo_metrics_test.go @@ -0,0 +1,59 @@ +package repository + +import ( + "context" + "database/sql/driver" + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/Wei-Shaw/sub2api/internal/service" +) + +func TestInsertSystemMetricsNullableIntegerMetrics(t *testing.T) { + createdAt := time.Date(2026, time.September, 4, 12, 0, 0, 0, time.UTC) + zero := 0 + + tests := []struct { + name string + dbConnActive *int + wantDBActive driver.Value + }{ + {name: "explicit zero is preserved", dbConnActive: &zero, wantDBActive: int64(0)}, + {name: "unavailable metric remains null", dbConnActive: nil, wantDBActive: nil}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + db, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("sqlmock.New: %v", err) + } + t.Cleanup(func() { _ = db.Close() }) + + args := make([]driver.Value, 40) + args[0] = createdAt + args[1] = int64(1) + for i := 4; i <= 12; i++ { + args[i] = int64(0) + } + args[35] = tt.wantDBActive + + mock.ExpectExec("INSERT INTO ops_system_metrics"). + WithArgs(args...). + WillReturnResult(sqlmock.NewResult(1, 1)) + + repo := &opsRepository{db: db} + err = repo.InsertSystemMetrics(context.Background(), &service.OpsInsertSystemMetricsInput{ + CreatedAt: createdAt, + DBConnActive: tt.dbConnActive, + }) + if err != nil { + t.Fatalf("InsertSystemMetrics: %v", err) + } + if err := mock.ExpectationsWereMet(); err != nil { + t.Fatalf("unmet SQL expectations: %v", err) + } + }) + } +}