Verify concurrent revision conflicts, null/string rejection, plain-user denials, and PG lock/read paths for the new policy key. Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
4.0 KiB
Go
143 lines
4.0 KiB
Go
package sysadmin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"testing"
|
|
|
|
"chat-agent-system/internal/db"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
func TestPGSettingsStore_RoundTrip(t *testing.T) {
|
|
dsn := db.SkipIfNoTestDatabaseURL(t)
|
|
db.RefuseSharedEnvDSN(t, dsn)
|
|
ctx := context.Background()
|
|
pool, err := pgxpool.New(ctx, dsn)
|
|
if err != nil {
|
|
t.Fatalf("connect: %v", err)
|
|
}
|
|
defer pool.Close()
|
|
if err := db.RunMigrationsOnceForTest(ctx, pool, dsn); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
|
|
store := newPGSettingsStore(pool)
|
|
key := "ui.maintenance_notice"
|
|
if _, _, _, _, err := store.Get(ctx, key); err != ErrSettingNotFound {
|
|
t.Fatalf("expected ErrSettingNotFound, got %v", err)
|
|
}
|
|
if _, _, err := store.Upsert(ctx, key, json.RawMessage(`"维护中"`), 1); err != nil {
|
|
t.Fatalf("upsert: %v", err)
|
|
}
|
|
raw, updatedBy, updatedAt, revision, err := store.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
if string(raw) != `"维护中"` {
|
|
t.Fatalf("unexpected value: %s", raw)
|
|
}
|
|
if updatedBy == nil || *updatedBy != 1 {
|
|
t.Fatalf("expected updatedBy=1, got %+v", updatedBy)
|
|
}
|
|
if updatedAt == nil {
|
|
t.Fatal("expected updatedAt")
|
|
}
|
|
if revision != 0 {
|
|
t.Fatalf("plain upsert must not bump revision, got %d", revision)
|
|
}
|
|
if _, _, err := store.Upsert(ctx, key, json.RawMessage(`"恢复"`), 2); err != nil {
|
|
t.Fatalf("second upsert: %v", err)
|
|
}
|
|
raw, updatedBy, _, _, err = store.Get(ctx, key)
|
|
if err != nil {
|
|
t.Fatalf("get after overwrite: %v", err)
|
|
}
|
|
if string(raw) != `"恢复"` || updatedBy == nil || *updatedBy != 2 {
|
|
t.Fatalf("expected overwrite, got %s by %+v", raw, updatedBy)
|
|
}
|
|
}
|
|
|
|
func TestPGSettingsStore_AuthorOnlyCASAndAudit(t *testing.T) {
|
|
dsn := db.SkipIfNoTestDatabaseURL(t)
|
|
db.RefuseSharedEnvDSN(t, dsn)
|
|
ctx := context.Background()
|
|
pool, err := pgxpool.New(ctx, dsn)
|
|
if err != nil {
|
|
t.Fatalf("connect: %v", err)
|
|
}
|
|
defer pool.Close()
|
|
if err := db.RunMigrationsOnceForTest(ctx, pool, dsn); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
|
|
store := newPGSettingsStore(pool)
|
|
svc := NewService(nil, Options{Store: store})
|
|
|
|
pol, err := svc.GetAuthorOnlyApprovalPolicy(ctx)
|
|
if err != nil {
|
|
t.Fatalf("get policy: %v", err)
|
|
}
|
|
if pol.Allowed {
|
|
t.Fatalf("seeded policy must default closed, got %+v", pol)
|
|
}
|
|
|
|
rev := pol.Revision
|
|
setting, err := svc.UpdateSettingEx(ctx, SettingUpdate{
|
|
Key: SettingKeyAllowAuthorOnlyApproval, Value: json.RawMessage(`true`),
|
|
UpdatedBy: 11, ExpectedRevision: &rev, RequestID: "pg-cas-1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("enable: %v", err)
|
|
}
|
|
if setting.Value != true || setting.Revision != rev+1 {
|
|
t.Fatalf("expected true@%d, got value=%v rev=%d", rev+1, setting.Value, setting.Revision)
|
|
}
|
|
|
|
_, err = svc.UpdateSettingEx(ctx, SettingUpdate{
|
|
Key: SettingKeyAllowAuthorOnlyApproval, Value: json.RawMessage(`false`),
|
|
UpdatedBy: 11, ExpectedRevision: &rev,
|
|
})
|
|
if !errors.Is(err, ErrSettingRevisionConflict) {
|
|
t.Fatalf("expected conflict, got %v", err)
|
|
}
|
|
|
|
var auditCount int
|
|
if err := pool.QueryRow(ctx, `
|
|
SELECT COUNT(*) FROM system_setting_policy_audits
|
|
WHERE setting_key = $1 AND request_id = 'pg-cas-1'
|
|
`, SettingKeyAllowAuthorOnlyApproval).Scan(&auditCount); err != nil {
|
|
t.Fatalf("count audits: %v", err)
|
|
}
|
|
if auditCount != 1 {
|
|
t.Fatalf("expected 1 audit row, got %d", auditCount)
|
|
}
|
|
|
|
tx, err := pool.Begin(ctx)
|
|
if err != nil {
|
|
t.Fatalf("begin: %v", err)
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
locked, err := svc.LockAuthorOnlyApprovalPolicy(ctx, tx)
|
|
if err != nil {
|
|
t.Fatalf("lock: %v", err)
|
|
}
|
|
if !locked.Allowed || locked.Revision != rev+1 {
|
|
t.Fatalf("locked snapshot mismatch: %+v", locked)
|
|
}
|
|
if err := tx.Commit(ctx); err != nil {
|
|
t.Fatalf("commit: %v", err)
|
|
}
|
|
|
|
// 复位为关闭,避免污染同库其它测试对默认态的假设
|
|
next := setting.Revision
|
|
if _, err := svc.UpdateSettingEx(ctx, SettingUpdate{
|
|
Key: SettingKeyAllowAuthorOnlyApproval, Value: json.RawMessage(`false`),
|
|
UpdatedBy: 11, ExpectedRevision: &next,
|
|
}); err != nil {
|
|
t.Fatalf("reset closed: %v", err)
|
|
}
|
|
}
|