fix(qwen): align Go hooks with Qwen Code 0.20
This commit is contained in:
1 parent
4521cbe32f
commit
dd72793cdb
18 files changed
+2921
-741
No files matched your search
@@ -0,0 +1,179 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestQwenSchemaTracksOfficialEventAndMatcherSets(t *testing.T) {
|
||||
expectedEvents := []string{
|
||||
"PreToolUse",
|
||||
"PostToolUse",
|
||||
"PostToolUseFailure",
|
||||
"PostToolBatch",
|
||||
"Notification",
|
||||
"UserPromptSubmit",
|
||||
"UserPromptExpansion",
|
||||
"SessionStart",
|
||||
"Stop",
|
||||
"MessageDisplay",
|
||||
"SubagentStart",
|
||||
"SubagentStop",
|
||||
"PreCompact",
|
||||
"PostCompact",
|
||||
"SessionEnd",
|
||||
"PermissionRequest",
|
||||
"PermissionDenied",
|
||||
"StopFailure",
|
||||
"TodoCreated",
|
||||
"TodoCompleted",
|
||||
"InstructionsLoaded",
|
||||
}
|
||||
if len(qwenEventNames) != len(expectedEvents) {
|
||||
t.Fatalf("known event count = %d", len(qwenEventNames))
|
||||
}
|
||||
for _, event := range expectedEvents {
|
||||
if _, exists := qwenEventNames[event]; !exists {
|
||||
t.Fatalf("official event is missing: %s", event)
|
||||
}
|
||||
}
|
||||
if len(qwenRegexMatcherEvents) != 15 {
|
||||
t.Fatalf("matcher event count = %d", len(qwenRegexMatcherEvents))
|
||||
}
|
||||
hooks := qwenHookSettings{}
|
||||
for _, event := range expectedEvents {
|
||||
hooks[event] = []any{map[string]any{
|
||||
"matcher": "tool-(?<name>.+)",
|
||||
"hooks": []any{},
|
||||
}}
|
||||
}
|
||||
entries := collectQwenRegexes([]qwenHookSettings{hooks})
|
||||
if len(entries) != 15 {
|
||||
t.Fatalf("collected matcher count = %d", len(entries))
|
||||
}
|
||||
}
|
||||
|
||||
func TestQwenMatcherValidationUsesJavaScriptRegExp(t *testing.T) {
|
||||
t.Run("JavaScript named capture", func(t *testing.T) {
|
||||
fixture := prepareQwenFixture(t, qwenFixtureOptions{
|
||||
settings: qwenString(`{
|
||||
"hooks": {
|
||||
"PreToolUse": [{ "matcher": "(?<tool>read|write)", "hooks": [] }]
|
||||
}
|
||||
}`),
|
||||
})
|
||||
installQwenFixture(t, fixture)
|
||||
})
|
||||
|
||||
t.Run("Python named capture", func(t *testing.T) {
|
||||
fixture := prepareQwenFixture(t, qwenFixtureOptions{
|
||||
settings: qwenString(`{
|
||||
"hooks": {
|
||||
"PreToolUse": [{ "matcher": "(?P<tool>read|write)", "hooks": [] }]
|
||||
}
|
||||
}`),
|
||||
})
|
||||
err := fixture.plugin.Install(fixture.config)
|
||||
if err == nil || !strings.Contains(err.Error(), "JavaScript regular expression") {
|
||||
t.Fatalf("install error = %v", err)
|
||||
}
|
||||
requireMissingQwenFile(t, fixture.guardPath)
|
||||
})
|
||||
}
|
||||
|
||||
func TestQwenSchemaAcceptsOfficialCommandHTTPAndPromptHandlers(t *testing.T) {
|
||||
settings := `{
|
||||
"hooks": {
|
||||
"PreToolUse": [{
|
||||
"matcher": "read|write",
|
||||
"sequential": true,
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"name": "command-hook",
|
||||
"command": "echo test",
|
||||
"shell": "powershell",
|
||||
"timeout": 1000,
|
||||
"env": { "MODE": "test" },
|
||||
"async": false,
|
||||
"description": "command"
|
||||
},
|
||||
{
|
||||
"type": "http",
|
||||
"name": "http-hook",
|
||||
"url": "https://hooks.example.test",
|
||||
"headers": { "Authorization": "Bearer token" },
|
||||
"allowedEnvVars": ["HOME"],
|
||||
"once": true,
|
||||
"if": "always"
|
||||
},
|
||||
{
|
||||
"type": "prompt",
|
||||
"name": "prompt-hook",
|
||||
"prompt": "Review this tool call",
|
||||
"model": "qwen3-coder"
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}`
|
||||
fixture := prepareQwenFixture(t, qwenFixtureOptions{settings: qwenString(settings)})
|
||||
installQwenFixture(t, fixture)
|
||||
raw := readQwenTestFile(t, fixture.configPath)
|
||||
for _, marker := range []string{"command-hook", "http-hook", "prompt-hook"} {
|
||||
if !strings.Contains(raw, marker) {
|
||||
t.Fatalf("handler %q was lost", marker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestQwenSchemaRejectsInvalidOfficialHandlerFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, handler, pattern string
|
||||
}{
|
||||
{
|
||||
"command env",
|
||||
`{"type":"command","command":"x","env":{"A":1}}`,
|
||||
"map names to strings",
|
||||
},
|
||||
{
|
||||
"command async",
|
||||
`{"type":"command","command":"x","async":"yes"}`,
|
||||
"must be a boolean",
|
||||
},
|
||||
{
|
||||
"command shell",
|
||||
`{"type":"command","command":"x","shell":"cmd"}`,
|
||||
"shell must be",
|
||||
},
|
||||
{
|
||||
"http headers",
|
||||
`{"type":"http","url":"https://x.test","headers":{"A":1}}`,
|
||||
"map names to strings",
|
||||
},
|
||||
{
|
||||
"http environment",
|
||||
`{"type":"http","url":"https://x.test","allowedEnvVars":[1]}`,
|
||||
"array of strings",
|
||||
},
|
||||
{
|
||||
"prompt model",
|
||||
`{"type":"prompt","prompt":"x","model":1}`,
|
||||
"must be a string",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
source := `{"hooks":{"PreToolUse":[{"hooks":[` + test.handler + `]}]}}`
|
||||
fixture := prepareQwenFixture(
|
||||
t,
|
||||
qwenFixtureOptions{settings: qwenString(source)},
|
||||
)
|
||||
err := fixture.plugin.Install(fixture.config)
|
||||
if err == nil || !strings.Contains(err.Error(), test.pattern) {
|
||||
t.Fatalf("install error = %v", err)
|
||||
}
|
||||
requireMissingQwenFile(t, fixture.guardPath)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user