package plugins import ( "os" "path/filepath" "strings" "testing" "time" core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git" ) func TestV2AssetsAndNetwork(t *testing.T) { host, _, dir := newHost(t) skills := filepath.Join(t.TempDir(), "skills") host.SetSkillsRoot(skills) root := filepath.Join(dir, "v2") for _, sub := range []string{"skills", "mcp", "knowledge", "parsers", "agents", "channels", "pages", "tools"} { if err := os.MkdirAll(filepath.Join(root, sub), 0o755); err != nil { t.Fatal(err) } } write := func(rel, body string) { t.Helper() if err := os.WriteFile(filepath.Join(root, rel), []byte(body), 0o644); err != nil { t.Fatal(err) } } write("plugin.json", `{ "id":"v2","name":"V2","version":"0.1.0","apiVersion":"`+core.PluginAPIVersionExperimental+`", "requires":{"network":["api.example.com"]}, "scope":{"tenants":["acme"]}, "backend":{"schemaVersion":1}, "provides":{"tools":["echo"],"skills":["note"],"mcpServers":["local"],"knowledge":["regs"],"parsers":["pdf"],"agents":["writer"],"channels":["feishu"],"pages":["home"]} }`) write("tools/echo.json", `{"name":"v2_echo","description":"e","parameters":{"type":"object"},"http":{"method":"GET","url":"https://api.example.com/x"}}`) write("skills/note.md", "---\nname: note\ndescription: 笔记技能\n---\n\n# note\n") write("mcp/local.json", `{"name":"local","command":"echo"}`) write("knowledge/regs.json", `{"id":"regs","title":"法规"}`) write("parsers/pdf.json", `{"id":"pdf","title":"PDF","mime":"application/pdf"}`) write("agents/writer.json", `{"id":"writer","title":"写作"}`) write("channels/feishu.json", `{"id":"feishu","kind":"feishu","title":"飞书"}`) write("pages/home.json", `{"id":"home","title":"首页","module":"./page.mjs"}`) if err := host.LoadDir()["v2"]; err != nil { t.Fatal(err) } ld := host.List()[0] if ld.Skills["note"] == "" || len(ld.MCP) != 1 || len(ld.Pages) != 1 || len(ld.Channels) != 1 { t.Fatalf("assets: %+v", ld) } if _, err := os.Stat(filepath.Join(skills, "plugin-v2", "note", "SKILL.md")); err != nil { t.Fatal(err) } if got := host.ListFor("other", ""); len(got) != 0 { t.Fatalf("scope leak: %d", len(got)) } if got := host.ListFor("acme", ""); len(got) != 1 { t.Fatalf("scope miss: %d", len(got)) } if err := host.st.SetPluginEnabled("acme", "v2", false); err != nil { t.Fatal(err) } if got := host.ListFor("acme", ""); len(got) != 0 { t.Fatal("grant should hide plugin") } } func TestExperimentalNetworkDeny(t *testing.T) { host, _, dir := newHost(t) root := filepath.Join(dir, "open") os.MkdirAll(filepath.Join(root, "tools"), 0o755) os.WriteFile(filepath.Join(root, "plugin.json"), []byte(`{"id":"open","name":"O","version":"0.1.0","apiVersion":"`+core.PluginAPIVersionExperimental+`","provides":{"tools":["e"]}}`), 0o644) os.WriteFile(filepath.Join(root, "tools", "e.json"), []byte(`{"name":"open_e","description":"e","parameters":{"type":"object"},"http":{"method":"GET","url":"https://example.com"}}`), 0o644) if err := host.LoadDir()["open"]; err == nil { t.Fatal("v2 without network allowlist must fail closed") } } func TestSidecarTouch(t *testing.T) { host, _, dir := newHost(t) host.SetHostAPI("http://127.0.0.1:1", "tok") root := filepath.Join(dir, "side") os.MkdirAll(root, 0o755) mark := filepath.Join(t.TempDir(), "up") os.WriteFile(filepath.Join(root, "plugin.json"), []byte(`{"id":"side","name":"S","version":"0.1.0","apiVersion":"1.0","backend":{"mode":"sidecar","command":"/bin/touch","args":["`+mark+`"],"schemaVersion":1},"provides":{}}`), 0o644) if err := host.LoadDir()["side"]; err != nil { t.Fatal(err) } deadline := time.Now().Add(2 * time.Second) for time.Now().Before(deadline) { if _, err := os.Stat(mark); err == nil { return } time.Sleep(20 * time.Millisecond) } t.Fatal("sidecar did not run") } // N2 WP4:不合规插件技能整批拒载,错误带插件上下文与字段定位。 func TestV2InvalidSkillRejected(t *testing.T) { host, _, dir := newHost(t) root := filepath.Join(dir, "badskill") if err := os.MkdirAll(filepath.Join(root, "skills"), 0o755); err != nil { t.Fatal(err) } write := func(rel, body string) { t.Helper() if err := os.WriteFile(filepath.Join(root, rel), []byte(body), 0o644); err != nil { t.Fatal(err) } } write("plugin.json", `{ "id":"badskill","name":"Bad","version":"0.1.0","apiVersion":"`+core.PluginAPIVersionExperimental+`", "backend":{"schemaVersion":1}, "provides":{"skills":["note"]} }`) // 缺 name(开放规范必填)。 write("skills/note.md", "---\ndescription: 没有名字\n---\n正文\n") err := host.LoadDir()["badskill"] if err == nil { t.Fatal("不合规技能必须拒载") } if !strings.Contains(err.Error(), "plugins: badskill: skill note:") || !strings.Contains(err.Error(), "name") { t.Fatalf("错误须带插件上下文与字段定位:%v", err) } }