返回 DeepSeek-Reasonix
present_test.go
根目录 / internal / tool / builtin / present_test.go
1 package builtin
2
3 import (
4 "context"
5 "encoding/json"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 toolpkg "reasonix/internal/tool"
12 )
13
14 func TestPresentValidatesAllFilesBeforePublishing(t *testing.T) {
15 dir := t.TempDir()
16 if err := os.WriteFile(filepath.Join(dir, "one.md"), []byte("one"), 0o600); err != nil {
17 t.Fatal(err)
18 }
19 presenter := present{workDir: dir}
20 ctx, collected := toolpkg.WithPresentedFilesCollector(context.Background())
21 _, err := presenter.Execute(ctx, json.RawMessage(`{"files":[{"path":"one.md"},{"path":"missing.md"}]}`))
22 if err == nil || !strings.Contains(err.Error(), "missing.md") {
23 t.Fatalf("expected missing file error, got %v", err)
24 }
25 if got := collected(); len(got) != 0 {
26 t.Fatalf("partial presentation published: %#v", got)
27 }
28 }
29
30 func TestPresentRecordsRelativePathsAndDescriptions(t *testing.T) {
31 dir := t.TempDir()
32 if err := os.WriteFile(filepath.Join(dir, "game.html"), []byte("ok"), 0o600); err != nil {
33 t.Fatal(err)
34 }
35 presenter := present{workDir: dir}
36 ctx, collected := toolpkg.WithPresentedFilesCollector(context.Background())
37 out, err := presenter.Execute(ctx, json.RawMessage(`{"files":[{"path":"./game.html","description":"Game"}]}`))
38 if err != nil {
39 t.Fatal(err)
40 }
41 if out != "Presented game.html" {
42 t.Fatalf("output = %q", out)
43 }
44 got := collected()
45 if len(got) != 1 || got[0].Path != "game.html" || got[0].Description != "Game" {
46 t.Fatalf("metadata = %#v", got)
47 }
48 }
49
50 func TestPresentRejectsFinalSymlink(t *testing.T) {
51 dir := t.TempDir()
52 target := filepath.Join(dir, "target.txt")
53 if err := os.WriteFile(target, []byte("ok"), 0o600); err != nil {
54 t.Fatal(err)
55 }
56 if err := os.Symlink(target, filepath.Join(dir, "link.txt")); err != nil {
57 t.Skipf("symlink unsupported: %v", err)
58 }
59 _, err := (present{workDir: dir}).Execute(context.Background(), json.RawMessage(`{"files":[{"path":"link.txt"}]}`))
60 if err == nil || !strings.Contains(err.Error(), "symbolic link") {
61 t.Fatalf("expected symlink error, got %v", err)
62 }
63 }
64
65 func TestPresentRejectsInvalidCardinalityAndDirectories(t *testing.T) {
66 dir := t.TempDir()
67 presenter := present{workDir: dir}
68 for _, args := range []string{
69 `{"files":[]}`,
70 `{"files":[{"path":"a"},{"path":"b"},{"path":"c"},{"path":"d"},{"path":"e"},{"path":"f"},{"path":"g"},{"path":"h"},{"path":"i"}]}`,
71 } {
72 if _, err := presenter.Execute(context.Background(), json.RawMessage(args)); err == nil {
73 t.Fatalf("invalid cardinality accepted: %s", args)
74 }
75 }
76 if _, err := presenter.Execute(context.Background(), json.RawMessage(`{"files":[{"path":"."}]}`)); err == nil || !strings.Contains(err.Error(), "not a regular file") {
77 t.Fatalf("directory error = %v", err)
78 }
79 }
80
81 func TestPresentAppliesResolvedForbidReadPolicy(t *testing.T) {
82 root := t.TempDir()
83 secret := filepath.Join(root, "secret")
84 if err := os.MkdirAll(secret, 0o700); err != nil {
85 t.Fatal(err)
86 }
87 if err := os.WriteFile(filepath.Join(secret, "token.txt"), []byte("secret"), 0o600); err != nil {
88 t.Fatal(err)
89 }
90 alias := filepath.Join(root, "alias")
91 if err := os.Symlink(secret, alias); err != nil {
92 t.Skipf("symlink unsupported: %v", err)
93 }
94 presenter := present{workDir: root, forbidRoots: []string{secret}}
95 ctx, collected := toolpkg.WithPresentedFilesCollector(context.Background())
96 if _, err := presenter.Execute(ctx, json.RawMessage(`{"files":[{"path":"alias/token.txt"}]}`)); err == nil {
97 t.Fatal("parent-directory symlink bypassed forbid_read")
98 }
99 if got := collected(); len(got) != 0 {
100 t.Fatalf("forbidden file was published: %#v", got)
101 }
102 }
103
104 func TestPresentRecordsAuthorizedAbsolutePath(t *testing.T) {
105 file := filepath.Join(t.TempDir(), "report.pdf")
106 if err := os.WriteFile(file, []byte("pdf"), 0o600); err != nil {
107 t.Fatal(err)
108 }
109 presenter := present{workDir: t.TempDir()}
110 ctx, collected := toolpkg.WithPresentedFilesCollector(context.Background())
111 args, err := json.Marshal(map[string]any{"files": []map[string]string{{"path": file}}})
112 if err != nil {
113 t.Fatal(err)
114 }
115 if _, err := presenter.Execute(ctx, args); err != nil {
116 t.Fatal(err)
117 }
118 if got := collected(); len(got) != 1 || got[0].Path != filepath.Clean(file) {
119 t.Fatalf("absolute metadata = %#v", got)
120 }
121 }
122
122 lines GO