package activator import ( "context" "errors" "testing" "time" ) func TestScheduledTokenFenceRefusesPreparation(t *testing.T) { a := &Service{} ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() if _, err := a.FenceAndStop(ctx, "token.example.com", 1); !errors.Is(err, ErrStopUnproven) { t.Fatalf("missing entry: %v", err) } called := false tok, err := a.PrepareScheduledToken("token.example.com", func() (string, error) { called = true return "secret", nil }) if called || tok != "" || !errors.Is(err, ErrFenced) { t.Fatalf("fenced preparation: called=%v token-present=%v err=%v", called, tok != "", err) } want := errors.New("prepare failed") _, err = a.PrepareScheduledToken("sibling.example.com", func() (string, error) { return "", want }) if !errors.Is(err, want) { t.Fatalf("unfenced sibling: %v", err) } } func TestScheduledTokenPreparationOrdersFence(t *testing.T) { t.Parallel() const host = "token.example.com" a := &Service{stopHistory: map[string]*stopObservation{host: { mainStop: true, mainExited: true, sidecarStop: true, sidecarExited: true, replicaClosed: true, }}} entered, release := make(chan struct{}), make(chan struct{}) done := make(chan error, 1) go func() { _, err := a.PrepareScheduledToken(host, func() (string, error) { close(entered) <-release return "token", nil }) done <- err }() select { case <-entered: case <-time.After(time.Second): t.Fatal("preparation did not start") } // Even with prior checked-stop history, a deadline cannot establish a // fence while previously admitted token I/O is still running. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond) proof, err := a.FenceAndStop(ctx, host, 1) cancel() a.mu.Lock() installed := a.fences[host] a.mu.Unlock() close(release) if installed != 0 { t.Fatal("fence installed before admitted IO finished") } if proof != nil || !errors.Is(err, ErrFenceNotInstalled) { t.Fatalf("fence crossed active preparation: proof=%v err=%v", proof != nil, err) } select { case err := <-done: if err != nil { t.Fatal(err) } case <-time.After(time.Second): t.Fatal("preparation did not finish") } ctx, cancel = context.WithTimeout(context.Background(), time.Second) defer cancel() proof, err = a.FenceAndStop(ctx, host, 1) if err != nil || !proof.Matches(host, 1) { t.Fatalf("fence after preparation: %v", err) } _, err = a.PrepareScheduledToken(host, func() (string, error) { t.Error("post-fence callback ran") return "", nil }) if !errors.Is(err, ErrFenced) { t.Fatal(err) } } func TestScheduledTokenInvalidCallback(t *testing.T) { for _, a := range []*Service{nil, {}} { if _, err := a.PrepareScheduledToken("token.example.com", nil); !errors.Is(err, ErrFenced) { t.Fatalf("nil callback: %v", err) } } }