1package subagents
2
3import (
4 "context"
5 "errors"
6 "path/filepath"
7 "testing"
8)
9
10func TestResidentCancellationPreservesInterruptedState(t *testing.T) {
11 for _, turnErr := range []error{context.Canceled, errors.Join(context.Canceled, errors.New("worker failed"))} {
12 t.Run(turnErr.Error(), func(t *testing.T) {
13 root := t.TempDir()
14 manager := NewResidentManager(root, func(ResidentChildSpec, *ResidentJournal) (ResidentRuntime, error) {
15 return ResidentTurnRunner(func(context.Context, string) error { return turnErr }), nil
16 })
17 t.Cleanup(func() { _ = manager.Close(context.Background()) })
18 spec := ResidentChildSpec{ID: "canceled", InitialTurnID: "initial", SessionID: "session", Provider: "openai", Model: "test", Required: true}
19 completions := make(chan ResidentCompletion, 1)
20 manager.SetCompletionObserver(func(c ResidentCompletion) bool { completions <- c; return true })
21 spawnCtx, cancel := context.WithCancel(t.Context())
22 defer cancel()
23 if _, err := manager.Spawn(spawnCtx, spec, "investigate"); err != nil {
24 t.Fatal(err)
25 }
26 cancel() // Accepted children outlive the caller's spawn context.
27 completion := awaitResidentCompletion(t, completions)
28 snapshot, ok := manager.SnapshotFor(spec.ID)
29 if !ok || snapshot.State != ResidentInterrupted || completion.Completion().Status != string(ResidentInterrupted) {
30 t.Fatalf("snapshot = %#v, completion = %#v", snapshot, completion)
31 }
32 result, err := manager.Result(spec.ID)
33 if err != nil || result.State != ResidentInterrupted || result.Handoff != "" || result.ErrorCode == residentV2ErrorBudgetExhausted {
34 t.Fatalf("result = %#v, error = %v", result, err)
35 }
36 if len(manager.UnmetRequired()) != 1 {
37 t.Fatal("cancellation satisfied required work")
38 }
39 if err := manager.Close(t.Context()); err != nil {
40 t.Fatal(err)
41 }
42 metadata, err := ReconcileResidentJournal(filepath.Join(root, spec.ID))
43 if err != nil || metadata.State != ResidentInterrupted {
44 t.Fatalf("reconciled metadata = %#v, error = %v", metadata, err)
45 }
46 })
47 }
48}