Files
Agent-System-Backend/internal/server/compiled_list_test.go
T
weichen 9aa51e5250
ci / vet-and-test (pull_request) Successful in 47s
List compiled plugins in /api/plugins
Why: compiled-in plugins (generative-ui, rdm) never appeared in the admin
plugins list: GET /api/plugins only scanned the directory host, so binary-
level capabilities were invisible to operators (user-reported gap).
Evidence: go test ./internal/app/ ./internal/server/ green locally incl.
new TestApplyCompiledRecordsSummary and TestListPluginsMergesCompiled;
CI (make test) is authoritative.
Impact: applyCompiled records each plugin's registered commands/intents/
skills into a summary handed to the server; /api/plugins merges synthesized
inproc entries (backend.mode=inproc, apiVersion=extension surface version,
provides from the registration record). Directory entries win on id
collision. No public extension-surface change.
Refs: WP3-I4; task card feature-plugins-compiled-listing.
2026-10-12 01:44:56 +00:00

125 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package server
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"agent-system-backend/internal/bus"
"agent-system-backend/internal/plugins"
"agent-system-backend/internal/profiles"
"agent-system-backend/internal/runmgr"
"agent-system-backend/internal/runtime"
"agent-system-backend/internal/settings"
"agent-system-backend/internal/store"
"agent-system-backend/internal/tools"
"agent-system-backend/pkg/extension"
)
type compiledListView struct {
Manifest struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
APIVersion string `json:"apiVersion"`
Provides struct {
Commands []string `json:"commands"`
Intents []string `json:"intents"`
} `json:"provides"`
Backend struct {
Mode string `json:"mode"`
} `json:"backend"`
} `json:"manifest"`
}
// WP3-I4:/api/plugins 合并编译插件条目——backend.mode=inproc、apiVersion 取
// 扩展面版本、provides 来自注册面摘要;目录插件同名时目录条目权威不重复。
func TestListPluginsMergesCompiled(t *testing.T) {
dir := t.TempDir()
st, err := store.OpenSQLite(filepath.Join(dir, "test.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
b := bus.New()
reg := tools.NewRegistry()
reg.RegisterBuiltin()
setSvc := settings.NewService(st)
profSvc := profiles.New(st)
profSvc.EnsureDefault()
mgr := runmgr.New(st, b, reg, profSvc, setSvc, &runtime.LLMRunner{}, &runtime.SubLLMRunner{}, &runtime.MockRunner{}, &runtime.MockSubagent{})
pluginsRoot := filepath.Join(dir, "plugins")
// 目录插件与编译条目同名:目录条目权威,编译合成条目不重复列出。
if err := os.MkdirAll(filepath.Join(pluginsRoot, "generative-ui"), 0o755); err != nil {
t.Fatal(err)
}
manifest := `{"id":"generative-ui","name":"目录版","version":"1.2.3","apiVersion":"1.0","provides":{}}`
if err := os.WriteFile(filepath.Join(pluginsRoot, "generative-ui", "plugin.json"), []byte(manifest), 0o644); err != nil {
t.Fatal(err)
}
ph := plugins.New(st, reg, setSvc, pluginsRoot)
for id, err := range ph.LoadDir() {
if err != nil {
t.Fatalf("LoadDir %s: %v", id, err)
}
}
srv := New(st, mgr, reg, profSvc, setSvc, ph)
srv.Auth = NewAuthenticator(st, true)
srv.CompiledPlugins = []CompiledPlugin{
{ID: "rdm", Commands: []string{"rdm.ingest", "rdm.inspect.capabilities"}, Intents: []string{"rdm.write-proposal"}},
{ID: "generative-ui", Commands: []string{"generative-ui.present"}},
}
h := srv.Handler()
key, err := st.CreateAPIKey("alice", "e2e")
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodGet, "/api/plugins", nil)
req.Header.Set("Authorization", "Bearer "+key)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
}
var list []compiledListView
if err := json.Unmarshal(rec.Body.Bytes(), &list); err != nil {
t.Fatal(err)
}
var rdm, genui *compiledListView
genuiCount := 0
for i := range list {
switch list[i].Manifest.ID {
case "rdm":
rdm = &list[i]
case "generative-ui":
genui = &list[i]
genuiCount++
}
}
if rdm == nil {
t.Fatalf("compiled rdm entry missing: %s", rec.Body.String())
}
if rdm.Manifest.Name != "rdm" || rdm.Manifest.Backend.Mode != "inproc" {
t.Fatalf("rdm entry = %+v", rdm.Manifest)
}
if rdm.Manifest.APIVersion != extension.SurfaceVersion {
t.Fatalf("rdm apiVersion = %q, want %q", rdm.Manifest.APIVersion, extension.SurfaceVersion)
}
if len(rdm.Manifest.Provides.Commands) != 2 || len(rdm.Manifest.Provides.Intents) != 1 {
t.Fatalf("rdm provides = %+v", rdm.Manifest.Provides)
}
if genuiCount != 1 || genui == nil {
t.Fatalf("generative-ui listed %d times", genuiCount)
}
// 目录条目权威:同名编译条目不重复、manifest 不被合成版覆盖。
if genui.Manifest.Name != "目录版" || genui.Manifest.Version != "1.2.3" || genui.Manifest.Backend.Mode != "" {
t.Fatalf("genui entry = %+v", genui.Manifest)
}
}