返回 DeepSeek-Reasonix
remote_presented_file_test.go
根目录 / desktop / remote_presented_file_test.go
1 package main
2
3 import (
4 "encoding/json"
5 "net/http"
6 "testing"
7 )
8
9 func TestRemotePresentedFileDeclaredRequiresTrustedPresentRecord(t *testing.T) {
10 history := json.RawMessage(`[
11 {"role":"tool","toolCallId":"present-1","toolName":"present","presentedFiles":[{"path":"dist/game.html","description":"game"}]},
12 {"role":"tool","toolCallId":"plugin-1","toolName":"plugin.present","presentedFiles":[{"path":"dist/fake.html"}]},
13 {"role":"assistant","toolCallId":"present-2","toolName":"present","presentedFiles":[{"path":"dist/fake-2.html"}]}
14 ]`)
15 if !remotePresentedFileDeclared(history, "present-1", "dist/game.html") {
16 t.Fatal("trusted present record was not recognized")
17 }
18 for _, test := range []struct{ call, path string }{
19 {"present-1", "dist/other.html"},
20 {"plugin-1", "dist/fake.html"},
21 {"present-2", "dist/fake-2.html"},
22 {"", "dist/game.html"},
23 } {
24 if remotePresentedFileDeclared(history, test.call, test.path) {
25 t.Fatalf("untrusted remote record accepted: call=%q path=%q", test.call, test.path)
26 }
27 }
28 if remotePresentedFileDeclared(json.RawMessage(`not-json`), "present-1", "dist/game.html") {
29 t.Fatal("invalid history must fail closed")
30 }
31 }
32
33 func TestRemoteWorkspaceArtifactDeclaredRequiresSuccessfulNativeMutation(t *testing.T) {
34 history := json.RawMessage(`[
35 {"role":"assistant","toolCalls":[
36 {"id":"write-1","name":"write_file","arguments":"{\"path\":\"dist/report.md\"}"},
37 {"id":"move-1","name":"move_file","arguments":"{\"source_path\":\"a.md\",\"destination_path\":\"dist/moved.md\"}"},
38 {"id":"plugin-1","name":"plugin_write","arguments":"{\"path\":\"dist/fake.md\"}"}
39 ]},
40 {"role":"tool","toolCallId":"write-1","toolName":"write_file","content":"wrote dist/report.md"},
41 {"role":"tool","toolCallId":"move-1","toolName":"move_file","toolResultError":"denied"},
42 {"role":"tool","toolCallId":"plugin-1","toolName":"plugin_write","content":"ok"}
43 ]`)
44 if !remoteWorkspaceArtifactDeclared(history, "write-1", "dist/report.md") {
45 t.Fatal("successful native mutation was not recognized")
46 }
47 for _, test := range []struct{ call, path string }{
48 {"write-1", "dist/other.md"},
49 {"move-1", "dist/moved.md"},
50 {"plugin-1", "dist/fake.md"},
51 {"", "dist/report.md"},
52 } {
53 if remoteWorkspaceArtifactDeclared(history, test.call, test.path) {
54 t.Fatalf("untrusted remote artifact accepted: call=%q path=%q", test.call, test.path)
55 }
56 }
57 }
58
59 func TestRemotePresentedFilesFenceRejectsRouteAndConnectionChanges(t *testing.T) {
60 client := &http.Client{}
61 tab := &remoteTab{
62 id: "remote-present",
63 ref: RemoteTabRef{HostID: "host-a"},
64 state: "ready",
65 client: client,
66 base: "http://127.0.0.1:43120",
67 gen: 7,
68 selectionRevision: 3,
69 capabilities: map[string]bool{"present-files-v1": true},
70 routing: remoteTabSessionRouting{
71 currentPath: "/remote/workspace/.reasonix/sessions/one.jsonl",
72 },
73 }
74 app := &App{remoteTabs: map[string]*remoteTab{tab.id: tab}}
75 fence, err := app.requireRemotePresentedFiles(tab.id, "host-a")
76 if err != nil {
77 t.Fatalf("require remote present support: %v", err)
78 }
79 if !app.remotePresentedFilesFenceCurrent(fence) {
80 t.Fatal("fresh remote present fence should be current")
81 }
82
83 for _, mutate := range []func(){
84 func() { tab.gen++ },
85 func() { tab.selectionRevision++ },
86 func() { tab.client = &http.Client{} },
87 func() { tab.routing.currentPath = "/remote/workspace/.reasonix/sessions/two.jsonl" },
88 func() { tab.routing.rehydratingPath = tab.routing.currentPath },
89 } {
90 mutate()
91 if app.remotePresentedFilesFenceCurrent(fence) {
92 t.Fatal("stale remote present fence was accepted")
93 }
94 tab.client = client
95 tab.gen = fence.generation
96 tab.selectionRevision = fence.selectionRevision
97 tab.routing.currentPath = fence.sessionPath
98 tab.routing.rehydratingPath = ""
99 }
100 }
101
102 func TestRemotePresentedAbsolutePathUsesSourceWorkspaceStyle(t *testing.T) {
103 for _, test := range []struct{ workspace, presented, want string }{
104 {"/srv/project", "dist/game.html", "/srv/project/dist/game.html"},
105 {"/srv/project", "/tmp/report.pdf", "/tmp/report.pdf"},
106 {`C:\work\project`, `dist\game.html`, `C:\work\project\dist\game.html`},
107 {`C:\work\project`, `D:\out\report.pdf`, `D:\out\report.pdf`},
108 } {
109 if got := remotePresentedAbsolutePath(test.workspace, test.presented); got != test.want {
110 t.Errorf("remotePresentedAbsolutePath(%q, %q) = %q, want %q", test.workspace, test.presented, got, test.want)
111 }
112 }
113 }
114
115 func TestConfinedRemoteWorkspacePath(t *testing.T) {
116 for _, test := range []struct {
117 workspace string
118 requested string
119 want string
120 allowed bool
121 }{
122 {"/srv/project", "dist/report.md", "/srv/project/dist/report.md", true},
123 {"/srv/project", "/srv/project/dist/report.md", "/srv/project/dist/report.md", true},
124 {"/srv/project", "../secret.txt", "", false},
125 {"/srv/project", "/srv/project-old/secret.txt", "", false},
126 {`C:\work\project`, `dist\report.md`, `C:\work\project\dist\report.md`, true},
127 {`C:\work\project`, `C:\work\project\dist\report.md`, `C:\work\project\dist\report.md`, true},
128 {`C:\work\project`, `D:\secret.txt`, "", false},
129 } {
130 got, err := confinedRemoteWorkspacePath(test.workspace, test.requested)
131 if test.allowed && (err != nil || got != test.want) {
132 t.Errorf("confinedRemoteWorkspacePath(%q, %q) = %q, %v; want %q", test.workspace, test.requested, got, err, test.want)
133 }
134 if !test.allowed && err == nil {
135 t.Errorf("confinedRemoteWorkspacePath(%q, %q) unexpectedly allowed %q", test.workspace, test.requested, got)
136 }
137 }
138 }
139
139 lines GO