159 lines
6.0 KiB
Go
159 lines
6.0 KiB
Go
package plugins
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestQwenInstallRejectsMalformedSettingsBeforeWrites(t *testing.T) {
|
|
tests := []struct {
|
|
name, source, pattern string
|
|
}{
|
|
{"malformed", "{ malformed", "parse Qwen Code settings"},
|
|
{"non-object", "[]", "JSON object"},
|
|
{"trailing comma", `{ "owner": true, }`, "trailing comma"},
|
|
{"duplicate root", `{ "hooks": {}, "hooks": {} }`, "duplicate"},
|
|
{"nested duplicate", `{ "hooks": { "PreToolUse": [{ "hooks": [], "hooks": [] }] } }`, "duplicate"},
|
|
{"invalid disable flag", `{ "disableAllHooks": "yes" }`, "must be a boolean"},
|
|
{"hooks array", `{ "hooks": [] }`, "JSON object"},
|
|
{"unsupported event", `{ "hooks": { "UnknownEvent": [] } }`, "unsupported field"},
|
|
{"null event", `{ "hooks": { "PreToolUse": null } }`, "must be an array"},
|
|
{"null group", `{ "hooks": { "PreToolUse": [null] } }`, "must be an object"},
|
|
{"invalid matcher", `{ "hooks": { "PreToolUse": [{ "matcher": "[", "hooks": [] }] } }`, "regular expression"},
|
|
{"invalid sequential", `{ "hooks": { "PreToolUse": [{ "sequential": "yes", "hooks": [] }] } }`, "must be a boolean"},
|
|
{"null handlers", `{ "hooks": { "PreToolUse": [{ "hooks": null }] } }`, "hooks array"},
|
|
{"missing command", `{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "command" }] }] } }`, "non-empty string"},
|
|
{"missing url", `{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "http" }] }] } }`, "non-empty string"},
|
|
{"invalid handler type", `{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "function", "command": "x" }] }] } }`, "command"},
|
|
{"invalid timeout", `{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "command", "command": "x", "timeout": "ten" }] }] } }`, "finite number"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
fixture := prepareQwenFixture(t, qwenFixtureOptions{settings: qwenString(test.source)})
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), test.pattern) {
|
|
t.Fatalf("install error = %v, want substring %q", err, test.pattern)
|
|
}
|
|
if readQwenTestFile(t, fixture.configPath) != test.source {
|
|
t.Fatal("failed install changed Qwen Code settings")
|
|
}
|
|
for _, path := range []string{fixture.hookPath, fixture.runtimeConfig, fixture.privateKey} {
|
|
requireMissingQwenFile(t, path)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQwenInstallValidatesRoutingAndManagedPathsBeforeWrites(t *testing.T) {
|
|
t.Run("unreadable home env", func(t *testing.T) {
|
|
fixture := prepareQwenFixture(t, qwenFixtureOptions{})
|
|
envPath := filepath.Join(fixture.qwenDir, ".env")
|
|
if err := os.MkdirAll(envPath, 0755); err != nil {
|
|
t.Fatalf("create unreadable env path: %v", err)
|
|
}
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), "Qwen home environment") {
|
|
t.Fatalf("install error = %v", err)
|
|
}
|
|
requireMissingQwenFile(t, fixture.configPath)
|
|
requireMissingQwenFile(t, fixture.runtimeConfig)
|
|
})
|
|
|
|
tests := []struct {
|
|
name string
|
|
mutate func(*qwenFixture)
|
|
pattern string
|
|
}{
|
|
{
|
|
name: "missing guard",
|
|
mutate: func(fixture *qwenFixture) {
|
|
if err := os.Remove(fixture.guardPath); err != nil {
|
|
t.Fatalf("remove guard: %v", err)
|
|
}
|
|
},
|
|
pattern: "guard runtime is missing",
|
|
},
|
|
{
|
|
name: "invalid agent id",
|
|
mutate: func(fixture *qwenFixture) {
|
|
fixture.config.AgentID = "../agent"
|
|
},
|
|
pattern: "single non-empty path segment",
|
|
},
|
|
{
|
|
name: "guard outside managed directory",
|
|
mutate: func(fixture *qwenFixture) {
|
|
fixture.config.GuardScriptPath = filepath.Join(fixture.homeDir, "guard.js")
|
|
},
|
|
pattern: "managed agent directory",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
fixture := prepareQwenFixture(t, qwenFixtureOptions{})
|
|
test.mutate(fixture)
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), test.pattern) {
|
|
t.Fatalf("install error = %v, want substring %q", err, test.pattern)
|
|
}
|
|
for _, path := range []string{fixture.configPath, fixture.hookPath, fixture.runtimeConfig, fixture.privateKey} {
|
|
requireMissingQwenFile(t, path)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQwenTransactionRollsBackAllFilesAndCleansStaging(t *testing.T) {
|
|
fixture := prepareQwenFixture(t, qwenFixtureOptions{
|
|
settings: qwenJSON(map[string]any{"owner": "user"}),
|
|
})
|
|
original := readQwenTestFile(t, fixture.configPath)
|
|
failed := false
|
|
fixture.plugin.rename = func(source, destination string) error {
|
|
if !failed && sameQwenPath(destination, fixture.configPath) {
|
|
failed = true
|
|
return errors.New("simulated settings commit failure")
|
|
}
|
|
return os.Rename(source, destination)
|
|
}
|
|
err := fixture.plugin.Install(fixture.config)
|
|
if err == nil || !strings.Contains(err.Error(), "Write Qwen Code installation") || !failed {
|
|
t.Fatalf("transaction error = %v, failed = %v", err, failed)
|
|
}
|
|
if readQwenTestFile(t, fixture.configPath) != original {
|
|
t.Fatal("transaction rollback did not restore Qwen Code settings")
|
|
}
|
|
for _, path := range []string{fixture.hookPath, fixture.runtimeConfig, fixture.privateKey} {
|
|
requireMissingQwenFile(t, path)
|
|
}
|
|
requireNoQwenStagingFiles(t, fixture.homeDir)
|
|
}
|
|
|
|
func TestQwenRecoveryCommandsDoNotRequireNodeResolution(t *testing.T) {
|
|
fixture := prepareQwenFixture(t, qwenFixtureOptions{})
|
|
installQwenFixture(t, fixture)
|
|
t.Setenv("PATH", "")
|
|
status, err := fixture.plugin.Status()
|
|
if err != nil || !status.Installed {
|
|
t.Fatalf("status without PATH = %#v, %v", status, err)
|
|
}
|
|
if err := fixture.plugin.Uninstall(qwenTestAgentID); err != nil {
|
|
t.Fatalf("uninstall without PATH: %v", err)
|
|
}
|
|
requireMissingQwenFile(t, fixture.configPath)
|
|
}
|
|
|
|
func TestQwenStatusSurfacesMalformedRuntimeConfig(t *testing.T) {
|
|
fixture := prepareQwenFixture(t, qwenFixtureOptions{})
|
|
installQwenFixture(t, fixture)
|
|
if err := os.WriteFile(fixture.runtimeConfig, []byte("{ malformed"), 0600); err != nil {
|
|
t.Fatalf("corrupt runtime config: %v", err)
|
|
}
|
|
if _, err := fixture.plugin.Status(); err == nil || !strings.Contains(err.Error(), "parse Elydora runtime config") {
|
|
t.Fatalf("status error = %v", err)
|
|
}
|
|
}
|