返回 DeepSeek-Reasonix
terminal_diagnostic_failure_test.go
根目录 / internal / control / terminal_diagnostic_failure_test.go
1 package control
2
3 import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "log/slog"
8 "slices"
9 "strings"
10 "testing"
11 "time"
12
13 "reasonix/internal/event"
14 "reasonix/internal/provider"
15 "reasonix/internal/session"
16 )
17
18 func TestTerminalEventsSurviveProviderDiagnosticFailure(t *testing.T) {
19 previous := slog.Default()
20 t.Cleanup(func() { slog.SetDefault(previous) })
21 for _, status := range []event.TurnStatus{event.TurnFailed, event.TurnInterrupted, event.TurnRecoveryRequired} {
22 t.Run(string(status), func(t *testing.T) {
23 var logs bytes.Buffer
24 slog.SetDefault(slog.New(slog.NewTextHandler(&logs, nil)))
25 c := &Controller{}
26 c.beginProviderDiagnosticTurn("turn")
27 // Use the real encoder's failure path, without a production test hook.
28 c.recordProviderRequest("turn", provider.RequestObservation{ID: 1, Phase: "request_started",
29 StartedAt: time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC), RemoteAddress: "private-peer"})
30 e := event.Event{Kind: event.TurnDone, TurnID: "turn", Status: status, Err: errors.New("fixture failure"), Cancelled: status == event.TurnInterrupted}
31 if status == event.TurnRecoveryRequired {
32 e.Recovery = &event.RecoveryStatus{State: "recovery_required", Reason: "uncooperative_tool"}
33 }
34 if _, err := c.providerDiagnosticEvent(e); err == nil {
35 t.Fatal("fixture did not fail diagnostic encoding")
36 }
37 projection := session.Projection{ActiveTools: map[string]string{"tool": "bash"},
38 StartedTools: map[string]bool{"tool": true}, Interactions: map[string]string{"approval": "pending"},
39 ActiveSteps: map[string]bool{"step": true}}
40 events, err := c.terminalSessionEvents(e, projection)
41 if err != nil {
42 t.Fatalf("optional diagnostic blocked terminal events: %v", err)
43 }
44 var kinds []string
45 for _, ev := range events {
46 kinds = append(kinds, ev.Kind)
47 }
48 want := []string{"tool/result", "interaction/resolved", "step/end"}
49 if e.Recovery != nil {
50 want = append(want, "runtime/recovery")
51 }
52 want = append(want, "turn/end")
53 if !slices.Equal(kinds, want) {
54 t.Fatalf("required closure events changed: %v", kinds)
55 }
56 var terminal struct{ Status event.TurnStatus }
57 if err := json.Unmarshal(events[len(events)-1].Payload, &terminal); err != nil || terminal.Status != status {
58 t.Fatalf("terminal status lost: %+v, err=%v", terminal, err)
59 }
60 if !strings.Contains(logs.String(), "WARN") || !strings.Contains(logs.String(), "discarded optional provider diagnostic") || strings.Contains(logs.String(), "private-peer") {
61 t.Fatalf("missing safe warning: %s", logs.String())
62 }
63 })
64 }
65 }
66
66 lines GO