package user import ( "context" "errors" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type fakeMLRepo struct { deleteN int64 deleteErr error cutoffSeen time.Time } func (r *fakeMLRepo) CreateMagicLinkToken(_ context.Context, _ *MagicLinkToken) error { return nil } func (r *fakeMLRepo) GetMagicLinkTokenByHash(_ context.Context, _ string) (*MagicLinkToken, error) { return nil, nil } func (r *fakeMLRepo) MarkMagicLinkTokenConsumed(_ context.Context, _ uint, _ time.Time) error { return nil } func (r *fakeMLRepo) DeleteExpiredMagicLinkTokens(_ context.Context, before time.Time) (int64, error) { r.cutoffSeen = before return r.deleteN, r.deleteErr } func TestRunOnce_ReturnsCount(t *testing.T) { repo := &fakeMLRepo{deleteN: 7} r := NewMagicLinkCleanupRunner(repo) n, err := r.runOnce(context.Background()) require.NoError(t, err) assert.EqualValues(t, 7, n) assert.WithinDuration(t, time.Now(), repo.cutoffSeen, time.Second) } func TestRunOnce_PropagatesError(t *testing.T) { repo := &fakeMLRepo{deleteErr: errors.New("simulated")} r := NewMagicLinkCleanupRunner(repo) _, err := r.runOnce(context.Background()) require.Error(t, err) } func TestStartCleanupLoop_StopsOnContextCancel(t *testing.T) { repo := &fakeMLRepo{} r := NewMagicLinkCleanupRunner(repo) ctx, cancel := context.WithCancel(context.Background()) r.StartCleanupLoop(ctx, 10*time.Millisecond) time.Sleep(25 * time.Millisecond) // 2 ticks cancel() time.Sleep(15 * time.Millisecond) // give the goroutine time to exit // Implicit assertion: no goroutine leak (test would hang in -race mode otherwise). } func TestStartCleanupLoop_NoOpWhenIntervalZero(t *testing.T) { repo := &fakeMLRepo{} r := NewMagicLinkCleanupRunner(repo) r.StartCleanupLoop(context.Background(), 0) // Just make sure no goroutine is started ; nothing observable to assert // beyond "no panic, returns immediately". }