Why: Account disable could be followed by late Backend business writes, API key creation could implicitly create users, account bootstrap locks could invert, and directory cursors were process-local or leaked out-of-scope changes. Evidence: Local go test ./... and isolated PostgreSQL package suites passed on the staged candidate; exact committed-head SELF replay and stage 6 human review remain pending. Impact: Adds final account transactions and external-effect checks across HTTP, Host, Bus and run manager; tightens API key issuance and persists versioned cursor signing. Delegated RDM rename still needs the separately proposed real-operator contract, so A1 and stage 6 are not complete. Refs: Documents U3 r1, approved A1-A4 amendment fb3a821, pending delegated-projection amendment 5783940, Dev feature/iam-u3-account-directory-recovery.
92 lines
2.9 KiB
Go
92 lines
2.9 KiB
Go
package server
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"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"
|
||
)
|
||
|
||
// WP3-I3:GET /api/admin-zone/sections 展平编译插件的分区声明并按
|
||
// Order→ID 稳定排序;无声明时空数组(非 null)。
|
||
func TestListAdminZoneSections(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{})
|
||
ph := plugins.New(st, reg, setSvc, filepath.Join(dir, "plugins"))
|
||
srv := New(st, mgr, reg, profSvc, setSvc, ph)
|
||
srv.Auth = NewAuthenticator(st, true)
|
||
srv.CompiledPlugins = []CompiledPlugin{
|
||
{ID: "rdm", AdminZones: []AdminZoneSection{
|
||
{ID: "rdm.audit", Title: "项目追溯与审计", Icon: "search", Order: 20},
|
||
}},
|
||
{ID: "other", AdminZones: []AdminZoneSection{
|
||
{ID: "other.z", Title: "Z", Order: 10},
|
||
{ID: "other.a", Title: "A", Order: 10},
|
||
}},
|
||
}
|
||
h := srv.Handler()
|
||
key, err := createLegacyAPIKeyFixture(t, st, "alice", "e2e")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
get := func(path string) *httptest.ResponseRecorder {
|
||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||
req.Header.Set("Authorization", "Bearer "+key)
|
||
rec := httptest.NewRecorder()
|
||
h.ServeHTTP(rec, req)
|
||
return rec
|
||
}
|
||
// 未认证拒绝。
|
||
if rec := httptest.NewRecorder(); func() *httptest.ResponseRecorder {
|
||
req := httptest.NewRequest(http.MethodGet, "/api/admin-zone/sections", nil)
|
||
h.ServeHTTP(rec, req)
|
||
return rec
|
||
}().Code != http.StatusUnauthorized {
|
||
t.Fatalf("unauthenticated request should 401")
|
||
}
|
||
rec := get("/api/admin-zone/sections")
|
||
if rec.Code != http.StatusOK {
|
||
t.Fatalf("status %d: %s", rec.Code, rec.Body.String())
|
||
}
|
||
var got []AdminZoneSection
|
||
if err := json.Unmarshal(rec.Body.Bytes(), &got); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
wantIDs := []string{"other.a", "other.z", "rdm.audit"} // Order 同为 10 的按 ID,再 Order 20
|
||
if len(got) != 3 {
|
||
t.Fatalf("sections = %+v", got)
|
||
}
|
||
for i, w := range wantIDs {
|
||
if got[i].ID != w {
|
||
t.Fatalf("sections[%d] = %s, want %s (full: %+v)", i, got[i].ID, w, got)
|
||
}
|
||
}
|
||
// 无声明时空数组非 null。
|
||
srv.CompiledPlugins = nil
|
||
if rec := get("/api/admin-zone/sections"); rec.Body.String() == "null" {
|
||
t.Fatalf("empty listing must be [] not null: %s", rec.Body.String())
|
||
}
|
||
}
|