Files
Agent-System-Backend/internal/settings/secrets_test.go
weichenandCursor 24bbb7b777
ci / vet-and-test (pull_request) Successful in 1m1s
Keep a newly seeded model key ahead of a stale secret
Why: The active secret_keys row overrides settings plaintext, so a replaced chat key kept returning 401. Migration also mutated the settings map in place, so clearing a matching plaintext never reached the database.
Evidence: go test ./internal/settings/ ./internal/runtime/ -count=1 (SELF, worktree).
Impact: System settings writes adopt a changed secret; conflicting plaintext is kept and logged; 401 errors name the key layer and an 8-hex fingerprint.
Refs: fix/model-key-secret-overlay
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-15 10:33:14 +00:00

130 lines
3.9 KiB
Go

package settings
import (
"bytes"
"log"
"path/filepath"
"strings"
"testing"
"agent-system-backend/internal/store"
)
func TestMigrateKeepsPlaintextWhenActiveSecretDiffers(t *testing.T) {
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "secrets.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
svc := NewService(st)
if _, err := st.AddSecret(OwnerSystemRow, "models.chat", "迁入", "sk-old"); err != nil {
t.Fatal(err)
}
if err := st.MergeSettingsScope(OwnerSystemRow, ScopeSystem, map[string]any{
"models": map[string]any{"chat": map[string]any{"apiKey": "sk-new"}},
}); err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
prev := log.Default().Writer()
log.SetOutput(&buf)
t.Cleanup(func() { log.SetOutput(prev) })
if err := svc.SnapshotAndMigrateSecrets(); err != nil {
t.Fatal(err)
}
cur, err := st.GetSettingsScope(OwnerSystemRow, ScopeSystem)
if err != nil {
t.Fatal(err)
}
if got, ok := lookupString(cur, "models.chat.apiKey"); !ok || got != "sk-new" {
t.Fatalf("conflicting plaintext = %q ok=%v", got, ok)
}
active, ok := st.ActiveSecret(OwnerSystemRow, "models.chat")
if !ok || active != "sk-old" {
t.Fatalf("active secret = %q ok=%v", active, ok)
}
if !strings.Contains(buf.String(), "models.chat") {
t.Fatalf("missing conflict warning: %s", buf.String())
}
var snaps int
if err := st.DB().QueryRow(`SELECT COUNT(*) FROM settings_snapshots WHERE owner=? AND scope=?`, OwnerSystemRow, ScopeSystem).Scan(&snaps); err != nil || snaps == 0 {
t.Fatalf("startup snapshot count=%d err=%v", snaps, err)
}
}
func TestMigrateClearsPlaintextWhenActiveSecretMatches(t *testing.T) {
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "secrets.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
svc := NewService(st)
if _, err := st.AddSecret(OwnerSystemRow, "models.chat", "迁入", "sk-same"); err != nil {
t.Fatal(err)
}
if err := st.MergeSettingsScope(OwnerSystemRow, ScopeSystem, map[string]any{
"models": map[string]any{"chat": map[string]any{"apiKey": "sk-same", "model": "keep-me"}},
}); err != nil {
t.Fatal(err)
}
if err := svc.SnapshotAndMigrateSecrets(); err != nil {
t.Fatal(err)
}
cur, err := st.GetSettingsScope(OwnerSystemRow, ScopeSystem)
if err != nil {
t.Fatal(err)
}
if _, ok := lookupString(cur, "models.chat.apiKey"); ok {
t.Fatalf("matching plaintext still stored: %#v", cur)
}
if got, _ := lookupString(cur, "models.chat.model"); got != "keep-me" {
t.Fatalf("non-secret field = %q", got)
}
active, ok := st.ActiveSecret(OwnerSystemRow, "models.chat")
if !ok || active != "sk-same" {
t.Fatalf("active secret = %q ok=%v", active, ok)
}
}
func TestSystemMergeAdoptsChangedSecret(t *testing.T) {
st, err := store.OpenSQLite(filepath.Join(t.TempDir(), "secrets.db"))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { st.Close() })
svc := NewService(st)
if _, err := st.AddSecret(OwnerSystemRow, "models.chat", "迁入", "sk-old"); err != nil {
t.Fatal(err)
}
if err := svc.MergeSystem(map[string]any{
"models": map[string]any{"chat": map[string]any{"apiKey": "sk-new"}},
}); err != nil {
t.Fatal(err)
}
active, ok := st.ActiveSecret(OwnerSystemRow, "models.chat")
if !ok || active != "sk-new" {
t.Fatalf("active secret = %q ok=%v", active, ok)
}
var label string
if err := st.DB().QueryRow(`SELECT label FROM secret_keys WHERE owner=? AND service=? AND active=1`, OwnerSystemRow, "models.chat").Scan(&label); err != nil {
t.Fatal(err)
}
if label != "播种" {
t.Fatalf("label = %q", label)
}
if err := svc.MergeSystem(map[string]any{
"models": map[string]any{"chat": map[string]any{"apiKey": "sk-new"}},
}); err != nil {
t.Fatal(err)
}
var n int
if err := st.DB().QueryRow(`SELECT COUNT(*) FROM secret_keys WHERE owner=? AND service=?`, OwnerSystemRow, "models.chat").Scan(&n); err != nil {
t.Fatal(err)
}
if n != 2 {
t.Fatalf("secret rows = %d, want the original plus one adoption", n)
}
}