Files
Agent-System-Backend/internal/settings/shared_test.go
weichen f965c5877f Enforce settings ownership and versioned shared configuration
Why: Personal project rows and credentials could override system policy and split shared business configuration by actor.

Evidence: SELF receipts /tmp/project-settings-backend-full-20261014-973d52c1.json and /tmp/rdm-settings-backend-final-green-20261014-03.json preserve full commands, object snapshots and output. PostgreSQL and current-head CI remain NOT_RUN.

Impact: Add scope-aware APIs, a fail-closed optional project configuration authorizer, CAS updates and explicit atomic migration. Preserve legacy rows and private preferences; protected secrets and runtime model routes obey system ownership.

Refs: P1/P2/P8, docs/records/2026-10-14-rdm-settings-completion-contract.md
2026-10-14 02:28:41 +00:00

74 lines
2.7 KiB
Go

package settings
import (
"agent-system-backend/internal/store"
"path/filepath"
"testing"
)
func TestProjectBusinessSettingsSharedButPersonalPreferencesPrivate(t *testing.T) {
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "shared.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
s := NewService(st)
if err := s.MergeProject("alice", "p1", map[string]any{"deliverables.style": "formal"}); err != nil {
t.Fatal(err)
}
if got := s.Effective("bob", "p1").GetString("deliverables.style", ""); got != "formal" {
t.Fatalf("same project's second member got %q", got)
}
if got := s.Effective("alice", "p2").GetString("deliverables.style", ""); got != "modern" {
t.Fatalf("settings crossed project: %q", got)
}
// Personal project preferences and old managed settings stay isolated.
if err := st.MergeSettingsScope("alice", ScopeProjectPrefix+"p1", map[string]any{"crossSession.read": true, "ui.theme": "light", "context.userInstructions": "private", "models.chat.model": "legacy"}); err != nil {
t.Fatal(err)
}
if s.Effective("bob", "p1").GetBool("crossSession.read", false) || s.Effective("bob", "p1").GetString("context.userInstructions", "") != "" {
t.Fatal("personal project preferences leaked across actors")
}
if s.Effective("alice", "p1").GetString("models.chat.model", "") != "" {
t.Fatal("legacy managed model override remains effective")
}
}
func TestProtectedCredentialsIgnoreOldPersonalRows(t *testing.T) {
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "secrets.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
s := NewService(st)
for _, spec := range secretSpecs {
if _, err := st.AddSecret("alice", spec.service, "legacy", "personal-fixture"); err != nil {
t.Fatal(err)
}
if _, err := st.AddSecret(OwnerSystemRow, spec.service, "system", "system-fixture"); err != nil {
t.Fatal(err)
}
if got, ok := s.LookupSecret("alice", spec.service); !ok || got != "system-fixture" {
t.Fatalf("personal credential won for %s", spec.service)
}
}
if err := s.MergeSystem(map[string]any{"models.chat.apiKey": "rotated-fixture"}); err != nil {
t.Fatal(err)
}
if got, _ := s.LookupSecret("alice", "models.chat"); got != "rotated-fixture" {
t.Fatal("new system form lost to old secret table row")
}
if err := s.SnapshotAndMigrateSecrets(); err != nil {
t.Fatal(err)
}
if got, _ := s.LookupSecret("alice", "models.chat"); got != "rotated-fixture" {
t.Fatal("restart migration revived old credential")
}
if err := s.MergeSystem(map[string]any{"models.chat.apiKey": ""}); err != nil {
t.Fatal(err)
}
if got, _ := s.LookupSecret("alice", "models.chat"); got != "" {
t.Fatal("revoked credential revived")
}
}