返回 DeepSeek-Reasonix
plugin_model_refs_test.go
根目录 / internal / installsource / plugin_model_refs_test.go
1 package installsource
2
3 import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "reasonix/internal/pluginpkg"
10 )
11
12 const testPluginConfig = `default_model = "plugin/commandcode/commandcode/deepseek-v4-flash"
13
14 [[providers]]
15 name = "deepseek-flash"
16 kind = "openai"
17 base_url = "https://api.deepseek.com/v1"
18 model = "deepseek-v4-flash"
19 api_key_env = "REASONIX_TEST_KEY"
20 `
21
22 // removePluginPackageFixture installs one plugin package into an isolated
23 // REASONIX_HOME and writes cfg as the user config. It returns the tool under
24 // test and the config path.
25 func removePluginPackageFixture(t *testing.T, cfg string) (*installSourceTool, string) {
26 t.Helper()
27
28 home := t.TempDir()
29 reasonixHome := filepath.Join(home, ".reasonix")
30 if err := os.MkdirAll(filepath.Join(reasonixHome, "plugins", "commandcode"), 0o755); err != nil {
31 t.Fatalf("mkdir plugin root: %v", err)
32 }
33 t.Setenv("HOME", home)
34 t.Setenv("USERPROFILE", home)
35 t.Setenv("REASONIX_HOME", reasonixHome)
36 // Provider entries resolve keys only from Reasonix's global .env, so the
37 // fallback provider counts as configured only when the key lives there.
38 if err := os.WriteFile(filepath.Join(reasonixHome, ".env"), []byte("REASONIX_TEST_KEY=sk-test\n"), 0o600); err != nil {
39 t.Fatalf("write credentials: %v", err)
40 }
41 oldUserHomeDir := userHomeDir
42 userHomeDir = func() (string, error) { return home, nil }
43 t.Cleanup(func() { userHomeDir = oldUserHomeDir })
44
45 if err := pluginpkg.Upsert(reasonixHome, pluginpkg.InstalledPlugin{
46 Name: "commandcode",
47 Root: filepath.Join("plugins", "commandcode"),
48 Version: "0.1.0",
49 Enabled: true,
50 }); err != nil {
51 t.Fatalf("upsert plugin: %v", err)
52 }
53
54 path := filepath.Join(reasonixHome, "config.toml")
55 if err := os.WriteFile(path, []byte(cfg), 0o600); err != nil {
56 t.Fatalf("write config: %v", err)
57 }
58
59 tl := NewTool(Options{ProjectRoot: t.TempDir()}).(*installSourceTool)
60 return tl, path
61 }
62
63 func TestRemovePluginPackageRepairsDefaultModel(t *testing.T) {
64 tl, path := removePluginPackageFixture(t, testPluginConfig)
65
66 act := &action{Kind: "plugin", Action: "remove_plugin_package", Name: "commandcode"}
67 if err := tl.applyRemovePluginPackage(request{}, act); err != nil {
68 t.Fatalf("applyRemovePluginPackage: %v", err)
69 }
70
71 got, err := os.ReadFile(path)
72 if err != nil {
73 t.Fatalf("read config: %v", err)
74 }
75 if strings.Contains(string(got), "plugin/commandcode/") {
76 t.Fatalf("config still references the removed plugin:\n%s", got)
77 }
78 if !strings.Contains(string(got), `default_model = "deepseek-flash"`) {
79 t.Fatalf("default_model was not repaired:\n%s", got)
80 }
81 }
82
83 func TestRemovePluginPackageLeavesUnrelatedConfigByteIdentical(t *testing.T) {
84 cfg := strings.Replace(testPluginConfig,
85 `default_model = "plugin/commandcode/commandcode/deepseek-v4-flash"`,
86 `default_model = "deepseek-flash"`, 1)
87 tl, path := removePluginPackageFixture(t, cfg)
88
89 act := &action{Kind: "plugin", Action: "remove_plugin_package", Name: "commandcode"}
90 if err := tl.applyRemovePluginPackage(request{}, act); err != nil {
91 t.Fatalf("applyRemovePluginPackage: %v", err)
92 }
93
94 got, err := os.ReadFile(path)
95 if err != nil {
96 t.Fatalf("read config: %v", err)
97 }
98 if string(got) != cfg {
99 t.Fatalf("config was rewritten although no ref named the plugin:\ngot:\n%s\nwant:\n%s", got, cfg)
100 }
101 }
102
103 func TestRemovePluginPackageKeepsMalformedConfig(t *testing.T) {
104 const broken = "default_model = \"plugin/commandcode/commandcode/deepseek-v4-flash\"\n[[providers\n"
105 tl, path := removePluginPackageFixture(t, broken)
106
107 act := &action{Kind: "plugin", Action: "remove_plugin_package", Name: "commandcode"}
108 if err := tl.applyRemovePluginPackage(request{}, act); err != nil {
109 t.Fatalf("applyRemovePluginPackage: %v", err)
110 }
111
112 got, err := os.ReadFile(path)
113 if err != nil {
114 t.Fatalf("read config: %v", err)
115 }
116 if string(got) != broken {
117 t.Fatalf("a malformed config must be left for the user to recover:\n%s", got)
118 }
119 if _, ok, _ := pluginpkg.FindInstalled(tl.reasonixHome, "commandcode"); ok {
120 t.Fatal("plugin should still be removed when the config cannot be repaired")
121 }
122 }
123
123 lines GO