diff --git a/cmd/launcher/registry_test.go b/cmd/launcher/registry_test.go index 83a6131..e3b4206 100644 --- a/cmd/launcher/registry_test.go +++ b/cmd/launcher/registry_test.go @@ -56,3 +56,59 @@ func TestReadReloadTarget_whitespace(t *testing.T) { t.Fatalf("expected 'asistente-2', got %q", got) } } + +// ── isSpecialConfig tests ───────────────────────────────────────────────── + +func TestIsSpecialConfig_matchesLoadedSpecial(t *testing.T) { + dir := t.TempDir() + cfg := filepath.Join(dir, "config.yaml") + if err := os.WriteFile(cfg, []byte(` +special: + id: orchestrator + type: orchestrator + enabled: true +llm: + primary: + provider: openai +`), 0o644); err != nil { + t.Fatal(err) + } + + loaded := map[string]bool{"orchestrator": true} + if !isSpecialConfig(cfg, loaded) { + t.Fatal("expected isSpecialConfig to return true for loaded orchestrator") + } +} + +func TestIsSpecialConfig_agentConfigNotSpecial(t *testing.T) { + dir := t.TempDir() + cfg := filepath.Join(dir, "config.yaml") + // An AgentConfig doesn't have special.id, so LoadSpecial will fail validation. + if err := os.WriteFile(cfg, []byte(` +agent: + id: father-bot + enabled: true +matrix: + homeserver: "https://example.com" + user_id: "@father:example.com" +llm: + primary: + provider: claude-code +`), 0o644); err != nil { + t.Fatal(err) + } + + loaded := map[string]bool{"orchestrator": true} + if isSpecialConfig(cfg, loaded) { + t.Fatal("expected isSpecialConfig to return false for agent config") + } +} + +func TestIsSpecialConfig_emptyLoadedMap(t *testing.T) { + if isSpecialConfig("any-path", nil) { + t.Fatal("expected false when no specials loaded") + } + if isSpecialConfig("any-path", map[string]bool{}) { + t.Fatal("expected false when empty specials map") + } +} diff --git a/pkg/security/security_test.go b/pkg/security/security_test.go index b6c7c3b..d1c81ed 100644 --- a/pkg/security/security_test.go +++ b/pkg/security/security_test.go @@ -140,7 +140,61 @@ func TestResolveACL_AccumulatedPermissions(t *testing.T) { } } -// 2.7 — agente referenciado directamente por ID en AgentPolicy.AgentGroup → recibe permisos +// 2.7 — privileged vs general: father-bot admin-only, general open to everyone +func TestResolveACL_PrivilegedVsGeneral(t *testing.T) { + p := makePolicy( + []security.UserGroup{ + {Name: "admins", Members: []string{"@admin:matrix.example.com"}}, + {Name: "everyone", Members: []string{"*"}}, + }, + []security.AgentGroup{ + {Name: "privileged", Agents: []string{"father-bot"}}, + {Name: "general", Agents: []string{"assistant-bot", "test-bot"}}, + }, + []security.AgentPolicy{ + { + AgentGroup: "privileged", + Permissions: []security.Permission{{UserGroup: "admins", Actions: []string{"*"}}}, + }, + { + AgentGroup: "general", + Permissions: []security.Permission{ + {UserGroup: "admins", Actions: []string{"*"}}, + {UserGroup: "everyone", Actions: []string{"*"}}, + }, + }, + }, + ) + + // father-bot: admin can interact, regular user cannot + fatherACL := security.ResolveACL("father-bot", p) + if fatherACL.Empty() { + t.Fatal("father-bot ACL should not be empty") + } + if !fatherACL.CanDo("@admin:matrix.example.com", "ask") { + t.Fatal("admin should be able to interact with father-bot") + } + if fatherACL.CanDo("@random:matrix.example.com", "ask") { + t.Fatal("non-admin should NOT be able to interact with father-bot") + } + + // assistant-bot: everyone can interact + assistantACL := security.ResolveACL("assistant-bot", p) + if assistantACL.Empty() { + t.Fatal("assistant-bot ACL should not be empty") + } + if !assistantACL.CanDo("@random:matrix.example.com", "ask") { + t.Fatal("everyone should be able to interact with assistant-bot") + } + + // unknown-bot: not in any group → empty ACL (open access) + unknownACL := security.ResolveACL("unknown-bot", p) + if !unknownACL.Empty() { + t.Fatal("unknown-bot should have empty ACL (open access)") + } +} + +// 2.8 — agente referenciado directamente por ID en AgentPolicy.AgentGroup → recibe permisos func TestResolveACL_DirectAgentID(t *testing.T) { p := makePolicy( []security.UserGroup{{Name: "admins", Members: []string{"@alice:matrix.org"}}},