返回 DeepSeek-Reasonix
preview_confine_test.go
根目录 / internal / tool / builtin / preview_confine_test.go
1 package builtin
2
3 import (
4 "context"
5 "encoding/json"
6 "os"
7 "path/filepath"
8 "testing"
9
10 "reasonix/internal/tool"
11 )
12
13 // Preview runs before the permission gate, so its read must be exactly as
14 // confined as Execute's write: an absolute path outside the workspace roots
15 // must error in Preview the way Execute would refuse it, instead of rendering
16 // the secret file's contents into the approval card and session log.
17 func TestPreviewRejectsPathsOutsideWriteRoots(t *testing.T) {
18 workspace := t.TempDir()
19 outside := t.TempDir() // a different tree: never inside the workspace
20 secret := filepath.Join(outside, "id_rsa")
21 if err := os.WriteFile(secret, []byte("SECRET MATERIAL\n"), 0o600); err != nil {
22 t.Fatal(err)
23 }
24 guard := NewSessionDataGuard(workspace, nil)
25 cases := map[string]map[string]any{
26 "write_file": {"path": secret, "content": "x"},
27 "edit_file": {"path": secret, "old_string": "SECRET", "new_string": "PWNED"},
28 "multi_edit": {"path": secret, "edits": []map[string]any{{"old_string": "SECRET", "new_string": "PWNED"}}},
29 "delete_range": {"path": secret, "start_anchor": "SECRET MATERIAL", "end_anchor": "SECRET MATERIAL"},
30 "delete_symbol": {"path": secret, "name": "secret"},
31 }
32 for _, managed := range []ManagedConfigPaths{
33 {},
34 NewManagedConfigPaths([]string{secret}),
35 } {
36 previews := map[string]tool.Previewer{}
37 for _, tl := range ConfineWriters([]string{workspace}, guard, managed) {
38 if p, ok := tl.(tool.Previewer); ok {
39 previews[tl.Name()] = p
40 }
41 }
42 for name, args := range cases {
43 p, ok := previews[name]
44 if !ok {
45 t.Fatalf("%s does not implement tool.Previewer", name)
46 }
47 raw, err := json.Marshal(args)
48 if err != nil {
49 t.Fatal(err)
50 }
51 if _, err := p.Preview(context.Background(), raw); err == nil {
52 t.Fatalf("%s previewed an out-of-roots path without error", name)
53 }
54 }
55 }
56 }
57
58 // The in-workspace happy path must keep working: Preview still renders a real
59 // diff for a file the write roots cover.
60 func TestPreviewAllowsInWorkspacePaths(t *testing.T) {
61 workspace := t.TempDir()
62 target := filepath.Join(workspace, "notes.txt")
63 if err := os.WriteFile(target, []byte("hello world\n"), 0o600); err != nil {
64 t.Fatal(err)
65 }
66 guard := NewSessionDataGuard(workspace, nil)
67 for _, tl := range ConfineWriters([]string{workspace}, guard, ManagedConfigPaths{}) {
68 p, ok := tl.(tool.Previewer)
69 if !ok {
70 continue
71 }
72 var args map[string]any
73 switch tl.Name() {
74 case "write_file":
75 args = map[string]any{"path": target, "content": "new\n"}
76 case "edit_file":
77 args = map[string]any{"path": target, "old_string": "world", "new_string": "reasonix"}
78 case "multi_edit":
79 args = map[string]any{"path": target, "edits": []map[string]any{{"old_string": "hello", "new_string": "hi"}}}
80 default:
81 continue
82 }
83 raw, err := json.Marshal(args)
84 if err != nil {
85 t.Fatal(err)
86 }
87 change, err := p.Preview(context.Background(), raw)
88 if err != nil {
89 t.Fatalf("%s preview inside workspace: %v", tl.Name(), err)
90 }
91 if change.Path == "" && change.NewText == "" && change.OldText == "" {
92 t.Fatalf("%s preview produced an empty change inside the workspace", tl.Name())
93 }
94 }
95 }
96
96 lines GO