Files
Agent-System-Backend/internal/app/scoped_host_test.go
T
weichen 7492feab42 Fence delegated project projection by its real operator
Why: RDM delegated rename rebuilt the creator Host and could commit a Backend project rename after the delegated operator was disabled.

Evidence: SQLite and isolated PostgreSQL targeted checks cover operator/creator disable order, two processes, sorted locks, auth-off local compatibility, and HTTP/command Host wiring; exact committed-head replay remains pending.

Impact: Adds a request-bound single-project extension capability and checks operator plus creator in the final Backend transaction. Global ScopedHost cannot derive delegated authority; RDM cross-database atomicity remains outside the promise.

Refs: Approved Documents delegated-projection amendment 5783940, U3 A1-A4 candidate adcd8fe, Dev feature/iam-u3-account-directory-recovery.
2026-10-14 08:17:31 +00:00

64 lines
2.1 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 app
import (
"context"
"path/filepath"
"testing"
"agent-system-backend/internal/rag"
"agent-system-backend/internal/settings"
"agent-system-backend/internal/store"
"agent-system-backend/pkg/extension"
)
// EXT-11 红测:注入前 NewScopedHost fail-closed;注入后重建句柄与分发期同构
// (owner 作用域、PluginID、KV 隔离、知识删除前缀强制)。
func TestScopedHostFactory(t *testing.T) {
// 注入前:fail-closed。
if _, err := extension.NewScopedHost("alice", "rdm"); err == nil {
t.Fatal("before wiring, NewScopedHost must fail closed(工厂未注入)")
}
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "t.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
if err := st.EnsureUser("alice", ""); err != nil {
t.Fatal(err)
}
set := settings.NewService(st)
pipe := rag.NewPipeline(st, set, nil, nil)
caps := hostcaps{st: st, pipe: pipe, set: set}
// 组合根同款注入(app.Run 内以 wireScopedHostFactory 接线;此处直接调内部函数)。
wireScopedHostFactory(caps)
t.Cleanup(func() { extension.SetScopedHostFactory(nil) })
h, err := extension.NewScopedHost("alice", "rdm")
if err != nil {
t.Fatalf("NewScopedHost: %v", err)
}
if h.Owner() != "alice" || h.PluginID() != "rdm" {
t.Fatalf("Owner/PluginID = %s/%s, want alice/rdm", h.Owner(), h.PluginID())
}
if _, ok := h.(extension.DelegatedProjectProjectionHost); ok {
t.Fatal("globally reconstructed Host exposed request-bound projection authority")
}
// KV 往返落 owner 作用域。
if err := h.KVPut("k1", "v1"); err != nil {
t.Fatal(err)
}
if v, ok, err := h.KVGet("k1"); err != nil || !ok || v != "v1" {
t.Fatalf("KVGet = %q ok=%v err=%v", v, ok, err)
}
// 同构性:知识删除前缀强制(manual 不可及)。
if err := h.KnowledgeDelete(context.Background(), "manual"); err == nil {
t.Fatal("scoped host must keep the plugin:<id> prefix enforcement")
}
// 空 owner/pluginID:拒绝。
if _, err := extension.NewScopedHost("", "rdm"); err == nil {
t.Fatal("empty owner must be rejected")
}
}