返回 DeepSeek-Reasonix
tabs_planner_notice_test.go
根目录 / desktop / tabs_planner_notice_test.go
1 package main
2
3 import (
4 "path/filepath"
5 "testing"
6
7 "reasonix/internal/event"
8 "reasonix/internal/provider"
9 )
10
11 // Planner notices are persisted through plannerDisplay into session history;
12 // dropping Detail here would make the expandable diagnostics disappear after
13 // a reload even though the live transcript showed them.
14 func TestRecordPlannerDisplayEventKeepsNoticeDetail(t *testing.T) {
15 tab := &WorkspaceTab{}
16 tab.recordDisplayEvent(event.Event{
17 Kind: event.Notice,
18 Level: event.LevelWarn,
19 Source: event.UsageSourcePlanner,
20 Text: "An MCP server failed to start.",
21 Detail: `mcp server "github" failed to start: command not found`,
22 })
23 messages := tab.takeDisplayTurn(false)
24 if len(messages) != 1 {
25 t.Fatalf("planner display len = %d, want 1", len(messages))
26 }
27 got := messages[0]
28 if got.Role != "notice" || got.Level != "warn" {
29 t.Fatalf("unexpected notice message: %+v", got)
30 }
31 if got.Content != "An MCP server failed to start." {
32 t.Fatalf("Content = %q", got.Content)
33 }
34 if got.Detail != `mcp server "github" failed to start: command not found` {
35 t.Fatalf("Detail = %q, want diagnostic detail preserved", got.Detail)
36 }
37 }
38
39 func TestCancelledDisplaySidecarReplaysStructuredDecisionReceipt(t *testing.T) {
40 const userContent = "run the build"
41 receipt := &provider.DecisionReceipt{
42 ID: "approval-1",
43 Kind: "tool",
44 Tool: "bash",
45 Subject: "npm run build",
46 Outcome: "allow_once",
47 }
48 tab := &WorkspaceTab{}
49 tab.recordDisplayEvent(event.Event{
50 Kind: event.Notice,
51 Level: event.LevelInfo,
52 Source: event.UsageSourceExecutor,
53 Text: "Decision recorded: allow_once",
54 Code: event.NoticeCodeDecisionReceipt,
55 DecisionReceipt: receipt,
56 })
57 display := tab.takeDisplayTurn(true)
58 if len(display) != 2 || display[0].DecisionReceipt == nil || display[1].Code != event.NoticeCodeCancelledTurn {
59 t.Fatalf("cancelled display = %+v, want structured receipt plus cancellation notice", display)
60 }
61
62 dir := t.TempDir()
63 sessionPath := filepath.Join(dir, "session.jsonl")
64 if err := recordSessionPlannerDisplay(dir, sessionPath, userContent, display); err != nil {
65 t.Fatalf("record cancelled display: %v", err)
66 }
67 plannerTurns := sessionPlannerDisplayTurns(dir, sessionPath)
68 if len(plannerTurns) != 1 || len(plannerTurns[0].Messages) != 2 || plannerTurns[0].Messages[0].DecisionReceipt == nil {
69 t.Fatalf("persisted display lost decision receipt: %+v", plannerTurns)
70 }
71
72 visible := historyMessagesWithPlannerDisplays([]provider.Message{
73 {Role: provider.RoleUser, Content: userContent},
74 {Role: provider.RoleAssistant, DecisionReceipts: []*provider.DecisionReceipt{receipt}},
75 }, func(content string) string { return content }, plannerTurns, nil)
76 if len(visible) != 3 {
77 t.Fatalf("visible history = %+v, want user, sidecar receipt, and cancellation notice", visible)
78 }
79 got := visible[1]
80 if got.Code != event.NoticeCodeDecisionReceipt || got.DecisionReceipt == nil {
81 t.Fatalf("structured decision receipt missing after reload: %+v", got)
82 }
83 if got.DecisionReceipt.ID != receipt.ID || got.DecisionReceipt.Tool != receipt.Tool || got.DecisionReceipt.Subject != receipt.Subject || got.DecisionReceipt.Outcome != receipt.Outcome {
84 t.Fatalf("replayed receipt = %+v, want %+v", got.DecisionReceipt, receipt)
85 }
86 for i, message := range visible {
87 if i != 1 && message.DecisionReceipt != nil {
88 t.Fatalf("decision receipt replayed more than once: %+v", visible)
89 }
90 }
91 }
92
92 lines GO