package database import ( "context" "sync" "sync/atomic" "testing" "github.com/stretchr/testify/assert" ) // AttachCounter / CounterFromContext are pure-Go helpers — no DB // needed. Tests here cover the contract that the gin middleware // + GORM callback rely on. func TestAttachCounter_FreshCounterIsZero(t *testing.T) { ctx, counter := AttachCounter(context.Background()) assert.NotNil(t, counter) assert.Equal(t, int64(0), atomic.LoadInt64(counter)) got := CounterFromContext(ctx) assert.Same(t, counter, got, "ctx-stored counter must be the same pointer") } func TestCounterFromContext_NilWhenNotAttached(t *testing.T) { got := CounterFromContext(context.Background()) assert.Nil(t, got, "ctx without AttachCounter must return nil") } func TestAttachCounter_PerRequestIsolation(t *testing.T) { // Each AttachCounter call returns its own pointer ; one // request's queries must not leak into another request's // counter. ctx1, c1 := AttachCounter(context.Background()) ctx2, c2 := AttachCounter(context.Background()) atomic.AddInt64(c1, 5) atomic.AddInt64(c2, 3) assert.Equal(t, int64(5), *CounterFromContext(ctx1)) assert.Equal(t, int64(3), *CounterFromContext(ctx2)) assert.NotSame(t, c1, c2) } func TestAttachCounter_ConcurrentIncrement(t *testing.T) { // The counter is bumped atomically from GORM callbacks ; in // real use multiple goroutines may share the same context // (e.g. errgroup parallel queries inside one request). Verify // 1000 concurrent increments land safely. _, counter := AttachCounter(context.Background()) const n = 1000 var wg sync.WaitGroup wg.Add(n) for i := 0; i < n; i++ { go func() { defer wg.Done() atomic.AddInt64(counter, 1) }() } wg.Wait() assert.Equal(t, int64(n), atomic.LoadInt64(counter)) } func TestReportRequestQueryCount_DisabledIsNoop(t *testing.T) { // When Enabled=false the call must not panic and not touch // the metrics ; the histogram is asserted by the metrics // package's own test, here we just verify the contract. ReportRequestQueryCount(N1DetectorConfig{Enabled: false}, "/api/v1/foo", 9999) } func TestReportRequestQueryCount_BelowThresholdSkipsLog(t *testing.T) { // With Logger=nil the function must not panic when count is // at or below threshold — the caller passes a nil logger when // the env-driven config doesn't supply one. ReportRequestQueryCount(N1DetectorConfig{Enabled: true, Threshold: 50}, "/api/v1/foo", 49) ReportRequestQueryCount(N1DetectorConfig{Enabled: true, Threshold: 50}, "/api/v1/foo", 50) } func TestReportRequestQueryCount_AboveThresholdNoLogger(t *testing.T) { // Above threshold but Logger=nil : must still bump the // suspicion counter (verified indirectly — no panic) and // not crash on the missing logger. ReportRequestQueryCount(N1DetectorConfig{Enabled: true, Threshold: 10}, "/api/v1/foo", 100) }