返回 DeepSeek-Reasonix
permission_rule_save_test.go
根目录 / desktop / permission_rule_save_test.go
1 package main
2
3 import (
4 "bytes"
5 "os"
6 "strings"
7 "testing"
8
9 "reasonix/internal/config"
10 "reasonix/internal/tool"
11 )
12
13 func TestAddPermissionRuleRejectsBareShellCommandBeforeSaving(t *testing.T) {
14 t.Setenv("REASONIX_HOME", t.TempDir())
15 if err := (&App{}).AddPermissionRule("deny", "rm"); err == nil || !strings.Contains(err.Error(), "Bash(rm:*)") {
16 t.Fatalf("AddPermissionRule(deny, rm) = %v, want a Bash rule suggestion", err)
17 }
18 if _, err := os.Stat(config.UserConfigPath()); !os.IsNotExist(err) {
19 t.Fatalf("rejected rule wrote config: stat error = %v", err)
20 }
21 cfg := config.Default()
22 cfg.Permissions.Deny = []string{"rm"} // Existing entries must not be rewritten.
23 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
24 t.Fatalf("save existing config: %v", err)
25 }
26 before, err := os.ReadFile(config.UserConfigPath())
27 if err != nil {
28 t.Fatal(err)
29 }
30 if err := (&App{}).AddPermissionRule("deny", "git reset"); err == nil {
31 t.Fatal("bare command was accepted into existing config")
32 }
33 after, err := os.ReadFile(config.UserConfigPath())
34 if err != nil || !bytes.Equal(before, after) {
35 t.Fatalf("rejected rule changed existing config: read error = %v", err)
36 }
37 }
38
39 func TestValidateSavedPermissionRuleUsesRegisteredTools(t *testing.T) {
40 registered := []tool.ContractEntry{{Name: "bash"}, {Name: "write_file"}, {Name: "plugin_custom"}}
41 for _, tc := range []struct {
42 list, rule, suggestion string
43 }{
44 {"deny", "rm", "Bash(rm:*)"},
45 {"ask", "git reset", "Bash(git reset:*)"},
46 {"allow", "git branch", "Bash(git branch)"},
47 } {
48 err := validateSavedPermissionRule(tc.list, tc.rule, registered, nil)
49 if err == nil || !strings.Contains(err.Error(), tc.suggestion) {
50 t.Errorf("%s %q: got %v, want %q suggestion", tc.list, tc.rule, err, tc.suggestion)
51 }
52 }
53 for _, rule := range []string{"Bash(rm:*)", "Edit(src/**)", "plugin_custom"} {
54 if err := validateSavedPermissionRule("deny", rule, registered, nil); err != nil {
55 t.Errorf("registered rule %q: %v", rule, err)
56 }
57 }
58 if err := validateSavedPermissionRule("deny", "Bash(rm:*)", tool.BuiltinContractEntries(), nil); err != nil {
59 t.Errorf("desktop built-in Bash rule: %v", err)
60 }
61 }
62
63 func TestAddPermissionRuleAcceptsConfiguredDisconnectedMCPServer(t *testing.T) {
64 t.Setenv("REASONIX_HOME", t.TempDir())
65 cfg := config.Default()
66 cfg.Plugins = []config.PluginEntry{{Name: "github", Type: "http", URL: "https://example.invalid/mcp"}}
67 if err := cfg.SaveTo(config.UserConfigPath()); err != nil {
68 t.Fatal(err)
69 }
70 app := &App{} // No active session or connected MCP registry.
71 for _, rule := range []string{"mcp__github__create_issue", "mcp_connect__github"} {
72 if err := app.AddPermissionRule("deny", rule); err != nil {
73 t.Fatalf("configured disconnected MCP rule %q: %v", rule, err)
74 }
75 }
76 if err := app.AddPermissionRule("deny", "mcp__other__create_issue"); err == nil {
77 t.Fatal("accepted an unconfigured MCP server")
78 }
79 if err := app.AddPermissionRule("deny", "mcp__github__*"); err == nil || !strings.Contains(err.Error(), "exact tool name") {
80 t.Fatalf("MCP tool-name glob = %v, want exact-name guidance", err)
81 }
82 got, err := config.LoadForEditReadOnlyStrict(config.UserConfigPath())
83 if err != nil {
84 t.Fatal(err)
85 }
86 if len(got.Permissions.Deny) != 2 || got.Permissions.Deny[0] != "mcp__github__create_issue" || got.Permissions.Deny[1] != "mcp_connect__github" {
87 t.Fatalf("saved MCP deny rules = %q", got.Permissions.Deny)
88 }
89 }
90
90 lines GO