package jobs_test import ( "context" "io" "log/slog" "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/yan1h/agent-coding-workflow/internal/jobs" ) func discardLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) } func TestScheduler_TickerFiresJob(t *testing.T) { t.Parallel() var calls atomic.Int32 s := jobs.NewScheduler(discardLogger()) s.Add("test", 20*time.Millisecond, true, func(ctx context.Context) { calls.Add(1) }) ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() s.Start(ctx) <-ctx.Done() s.Wait() assert.GreaterOrEqual(t, calls.Load(), int32(3), "expected at least 3 ticks in 200ms with 20ms interval") } func TestScheduler_DisabledNotRun(t *testing.T) { t.Parallel() var calls atomic.Int32 s := jobs.NewScheduler(discardLogger()) s.Add("disabled", 10*time.Millisecond, false, func(ctx context.Context) { calls.Add(1) }) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() s.Start(ctx) <-ctx.Done() s.Wait() assert.Equal(t, int32(0), calls.Load()) } func TestScheduler_StopOnCancel(t *testing.T) { t.Parallel() var calls atomic.Int32 s := jobs.NewScheduler(discardLogger()) s.Add("a", 5*time.Millisecond, true, func(ctx context.Context) { calls.Add(1) }) ctx, cancel := context.WithCancel(context.Background()) s.Start(ctx) time.Sleep(30 * time.Millisecond) cancel() s.Wait() first := calls.Load() time.Sleep(20 * time.Millisecond) require.Equal(t, first, calls.Load(), "ticks must stop after cancel") } func TestScheduler_RunnerPanicRecovered(t *testing.T) { t.Parallel() var calls atomic.Int32 s := jobs.NewScheduler(discardLogger()) s.Add("panicky", 10*time.Millisecond, true, func(ctx context.Context) { calls.Add(1) panic("boom") }) ctx, cancel := context.WithTimeout(context.Background(), 80*time.Millisecond) defer cancel() s.Start(ctx) <-ctx.Done() s.Wait() assert.GreaterOrEqual(t, calls.Load(), int32(2), "next tick should fire even if previous panicked") }