133 lines
3.7 KiB
Go
133 lines
3.7 KiB
Go
package control
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"reasonix/internal/event"
|
|
)
|
|
|
|
type approvalBlockingRunner struct {
|
|
c *Controller
|
|
}
|
|
|
|
func (r *approvalBlockingRunner) Run(ctx context.Context, _ string) error {
|
|
_, _, err := gateApprover{c: r.c}.Approve(ctx, "bash", "go test ./...", nil)
|
|
return err
|
|
}
|
|
|
|
type askBlockingRunner struct {
|
|
c *Controller
|
|
}
|
|
|
|
func (r *askBlockingRunner) Run(ctx context.Context, _ string) error {
|
|
_, err := r.c.Ask(ctx, []event.AskQuestion{{
|
|
ID: "choice",
|
|
Prompt: "Pick one",
|
|
Options: []event.AskOption{{Label: "A"}, {Label: "B"}},
|
|
}})
|
|
return err
|
|
}
|
|
|
|
func TestCancelClearsPendingApprovalRuntimeStatus(t *testing.T) {
|
|
approvals := make(chan event.Approval, 1)
|
|
done := make(chan event.Event, 1)
|
|
c := New(Options{Sink: event.FuncSink(func(e event.Event) {
|
|
switch e.Kind {
|
|
case event.ApprovalRequest:
|
|
approvals <- e.Approval
|
|
case event.TurnDone:
|
|
done <- e
|
|
}
|
|
})})
|
|
runner := &approvalBlockingRunner{c: c}
|
|
c.runner = runner
|
|
|
|
c.Send("needs approval")
|
|
select {
|
|
case <-approvals:
|
|
case <-time.After(30 * time.Second):
|
|
t.Fatal("timed out waiting for approval request")
|
|
}
|
|
if st := c.RuntimeStatus(); !st.Running || !st.PendingPrompt || !st.Cancellable || st.CancelRequested {
|
|
t.Fatalf("status before cancel = %+v, want running pending cancellable", st)
|
|
}
|
|
|
|
c.Cancel()
|
|
c.Cancel()
|
|
assertCancelClearedPendingRuntimeStatus(t, c.RuntimeStatus())
|
|
waitTurnDoneEvent(t, done)
|
|
// TurnDone is emitted inside the finishing window; Running() (and the
|
|
// RuntimeStatus it feeds) stays true until finishGuardedTurn's deferred
|
|
// clear runs. Wait for the gate to reopen before asserting idle.
|
|
waitIdle(t, c)
|
|
if st := c.RuntimeStatus(); st.Running || st.PendingPrompt || st.Cancellable || st.CancelRequested {
|
|
t.Fatalf("status after turn done = %+v, want idle", st)
|
|
}
|
|
}
|
|
|
|
func TestCancelClearsPendingAskRuntimeStatus(t *testing.T) {
|
|
asks := make(chan event.Ask, 1)
|
|
done := make(chan event.Event, 1)
|
|
c := New(Options{Sink: event.FuncSink(func(e event.Event) {
|
|
switch e.Kind {
|
|
case event.AskRequest:
|
|
asks <- e.Ask
|
|
case event.TurnDone:
|
|
done <- e
|
|
}
|
|
})})
|
|
runner := &askBlockingRunner{c: c}
|
|
c.runner = runner
|
|
|
|
c.Send("ask user")
|
|
select {
|
|
case <-asks:
|
|
case <-time.After(30 * time.Second):
|
|
t.Fatal("timed out waiting for ask request")
|
|
}
|
|
if st := c.RuntimeStatus(); !st.Running || !st.PendingPrompt || !st.Cancellable || st.CancelRequested {
|
|
t.Fatalf("status before cancel = %+v, want running pending cancellable", st)
|
|
}
|
|
|
|
c.Cancel()
|
|
assertCancelClearedPendingRuntimeStatus(t, c.RuntimeStatus())
|
|
waitTurnDoneEvent(t, done)
|
|
// TurnDone is emitted inside the finishing window; Running() (and the
|
|
// RuntimeStatus it feeds) stays true until finishGuardedTurn's deferred
|
|
// clear runs. Wait for the gate to reopen before asserting idle.
|
|
waitIdle(t, c)
|
|
if st := c.RuntimeStatus(); st.Running || st.PendingPrompt || st.Cancellable || st.CancelRequested {
|
|
t.Fatalf("status after turn done = %+v, want idle", st)
|
|
}
|
|
}
|
|
|
|
func assertCancelClearedPendingRuntimeStatus(t *testing.T, st RuntimeStatus) {
|
|
t.Helper()
|
|
if st.PendingPrompt {
|
|
t.Fatalf("status immediately after cancel = %+v, want pending prompt cleared", st)
|
|
}
|
|
if st.Running {
|
|
if !st.Cancellable || !st.CancelRequested {
|
|
t.Fatalf("status immediately after cancel = %+v, want running cancelling without pending prompt", st)
|
|
}
|
|
return
|
|
}
|
|
if st.Cancellable || st.CancelRequested {
|
|
t.Fatalf("status immediately after cancel = %+v, want idle when turn already completed", st)
|
|
}
|
|
}
|
|
|
|
func waitTurnDoneEvent(t *testing.T, done <-chan event.Event) {
|
|
t.Helper()
|
|
select {
|
|
case e := <-done:
|
|
if e.Kind != event.TurnDone {
|
|
t.Fatalf("event = %v, want TurnDone", e.Kind)
|
|
}
|
|
case <-time.After(30 * time.Second):
|
|
t.Fatal("timed out waiting for turn_done")
|
|
}
|
|
}
|