package privilege import ( "context" "errors" "sync/atomic" "testing" "time" ) type fakeAcquirer struct { validateErr error validates int32 refreshes int32 } func (f *fakeAcquirer) Validate(ctx context.Context) error { atomic.AddInt32(&f.validates, 1) return f.validateErr } func (f *fakeAcquirer) ValidateNonInteractive(ctx context.Context) error { atomic.AddInt32(&f.refreshes, 1) return nil } func TestAcquireFailsIfValidateFails(t *testing.T) { a := &fakeAcquirer{validateErr: errors.New("wrong password")} _, err := Acquire(context.Background(), a, 10*time.Millisecond) if err == nil { t.Fatal("expected error") } if atomic.LoadInt32(&a.refreshes) != 0 { t.Fatal("keepalive ran despite Validate failure") } } func TestAcquireStartsKeepaliveAndStops(t *testing.T) { a := &fakeAcquirer{} k, err := Acquire(context.Background(), a, 5*time.Millisecond) if err != nil { t.Fatal(err) } time.Sleep(25 * time.Millisecond) k.Stop() got := atomic.LoadInt32(&a.refreshes) if got < 2 { t.Fatalf("refreshes = %d, want >= 2", got) } // After Stop, no additional refreshes should occur. snap := atomic.LoadInt32(&a.refreshes) time.Sleep(20 * time.Millisecond) if after := atomic.LoadInt32(&a.refreshes); after != snap { t.Fatalf("refreshes continued after Stop: %d → %d", snap, after) } } func TestStopIsIdempotent(t *testing.T) { k, err := Acquire(context.Background(), &fakeAcquirer{}, 5*time.Millisecond) if err != nil { t.Fatal(err) } k.Stop() k.Stop() // must not panic or hang }