Files
Elydora-Go-SDK/cmd/elydora/plugins/qwen_recovery_test.go
T

296 lines
9.0 KiB
Go

package plugins
import (
"errors"
"os"
"path/filepath"
"strings"
"testing"
)
func TestQwenInstallRejectsMalformedKnownSettingsBeforeWrites(t *testing.T) {
tests := []struct {
name, source, pattern string
}{
{"malformed", "{ malformed", "parse Qwen Code user 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"},
{"invalid security", `{ "security": [] }`, "security"},
{
"invalid folder trust",
`{ "security": { "folderTrust": { "enabled": "yes" } } }`,
"must be a boolean",
},
{"hooks array", `{ "hooks": [] }`, "JSON object"},
{"null hooks", `{ "hooks": null }`, "JSON object"},
{"null event", `{ "hooks": { "PreToolUse": null } }`, "must be an array"},
{"null group", `{ "hooks": { "PreToolUse": [null] } }`, "must be an object"},
{
"invalid matcher type",
`{ "hooks": { "PreToolUse": [{ "matcher": 1, "hooks": [] }] } }`,
"matcher must be a string",
},
{
"invalid matcher regex",
`{ "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 command",
},
{
"missing url",
`{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "http" }] }] } }`,
"non-empty url",
},
{
"settings function",
`{ "hooks": { "PreToolUse": [{ "hooks": [{ "type": "function" }] }] } }`,
"unsupported type",
},
{
"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.guardPath,
fixture.hookPath,
fixture.runtimeConfig,
fixture.privateKey,
} {
requireMissingQwenFile(t, path)
}
})
}
}
func TestQwenPreservesFutureEventsAndLimitsRegexSemantics(t *testing.T) {
settings := `{
"hooks": {
"FutureEvent": { "shape": "future" },
"UserPromptSubmit": [{ "matcher": "[", "hooks": [] }]
}
}`
fixture := prepareQwenFixture(t, qwenFixtureOptions{settings: qwenString(settings)})
installQwenFixture(t, fixture)
raw := readQwenTestFile(t, fixture.configPath)
for _, marker := range []string{"FutureEvent", `"matcher": "["`} {
if !strings.Contains(raw, marker) {
t.Fatalf("future-compatible setting %q was lost: %s", marker, raw)
}
}
}
func TestQwenInstallRejectsMalformedReadOnlySourceBeforeWrites(t *testing.T) {
tests := []struct {
name string
path func(*qwenFixture) string
}{
{"system defaults", func(f *qwenFixture) string { return f.systemDefaults }},
{"workspace", func(f *qwenFixture) string { return f.workspaceConfig }},
{"system override", func(f *qwenFixture) string { return f.systemConfig }},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fixture := prepareQwenFixture(t, qwenFixtureOptions{})
filePath := test.path(fixture)
writeOptionalQwenFile(t, filePath, qwenString("{ malformed"))
err := fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), "parse Qwen Code") {
t.Fatalf("install error = %v", err)
}
if readQwenTestFile(t, filePath) != "{ malformed" {
t.Fatal("failed install changed the read-only source")
}
requireMissingQwenFile(t, fixture.guardPath)
})
}
}
func TestQwenInstallRejectsEffectiveDisableBeforeWrites(t *testing.T) {
fixture := prepareQwenFixture(t, qwenFixtureOptions{})
writeOptionalQwenFile(
t,
fixture.systemConfig,
qwenJSON(map[string]any{"disableAllHooks": true}),
)
err := fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), "disableAllHooks") ||
!strings.Contains(err.Error(), fixture.systemConfig) {
t.Fatalf("install error = %v", err)
}
requireMissingQwenFile(t, fixture.guardPath)
requireMissingQwenFile(t, fixture.configPath)
}
func TestQwenTransactionProtectsEveryReadOnlySource(t *testing.T) {
tests := []struct {
name string
configure func(*testing.T, *qwenFixture)
path func(*qwenFixture) string
content string
}{
{
"home environment",
func(*testing.T, *qwenFixture) {},
func(f *qwenFixture) string { return filepath.Join(f.qwenDir, ".env") },
"QWEN_HOME=external\n",
},
{
"system defaults",
func(*testing.T, *qwenFixture) {},
func(f *qwenFixture) string { return f.systemDefaults },
"{}\n",
},
{
"workspace",
func(*testing.T, *qwenFixture) {},
func(f *qwenFixture) string { return f.workspaceConfig },
"{}\n",
},
{
"system override",
func(*testing.T, *qwenFixture) {},
func(f *qwenFixture) string { return f.systemConfig },
"{}\n",
},
{
"trusted folders",
func(t *testing.T, f *qwenFixture) {
writeOptionalQwenFile(t, f.configPath, qwenJSON(map[string]any{
"security": map[string]any{
"folderTrust": map[string]any{"enabled": true},
},
}))
},
func(f *qwenFixture) string { return f.trustedFolders },
"{}\n",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fixture := prepareQwenFixture(t, qwenFixtureOptions{
settings: qwenJSON(map[string]any{"owner": "user"}),
})
test.configure(t, fixture)
original := readQwenTestFile(t, fixture.configPath)
sourcePath := test.path(fixture)
mutated := false
fixture.plugin.rename = func(source, destination string) error {
if !mutated {
mutated = true
writeOptionalQwenFile(t, sourcePath, qwenString(test.content))
}
return os.Rename(source, destination)
}
err := fixture.plugin.Install(fixture.config)
if err == nil || !strings.Contains(err.Error(), "changed during") || !mutated {
t.Fatalf("transaction error = %v, mutated = %v", err, mutated)
}
if readQwenTestFile(t, fixture.configPath) != original {
t.Fatal("transaction rollback changed user settings")
}
if readQwenTestFile(t, sourcePath) != test.content {
t.Fatal("transaction rollback overwrote the external mutation")
}
for _, path := range []string{
fixture.guardPath,
fixture.hookPath,
fixture.runtimeConfig,
fixture.privateKey,
} {
requireMissingQwenFile(t, path)
}
requireNoQwenStagingFiles(t, fixture.homeDir)
})
}
}
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(), "Install Qwen Code hooks") || !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.guardPath,
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)
}
}