Files
Agent-System-Backend/internal/store/role_policy_migration_test.go
T
weichenandCursor b4a19e784d
governance / governance (pull_request) Successful in 18s
ci / vet-and-test (pull_request) Successful in 1m5s
Merge Backend main into IAM U4 role authority
Why: the U4 branch is 89 commits behind main, which now carries the shared
document renderer, two-trees project work, the SQLite instance guard and
exec env layers (PR #209 merged as c76d8bd); it conflicted in storage
migrations, host capabilities and the extension surface. The user authorized
syncing U4 after the renderer line on 2026-10-18.
Evidence: content is the agent-exec transport 3b64eb5 (cursor worker, unit
backend-iam-u4-onto-main-c76d8bd-r1, run on a checkout whose tree equals main,
receipt /tmp/cleanup-20261018/render/u4-backend-onto-run.json, SELF). Host
check: main migrations 1-48 unchanged (migrate.go has no deleted lines versus
main), U4 migrations moved from 35-36 to 49-50 with identical SQL,
surface.txt is the sorted union, deps stay at main's pins.
Impact: databases that ran unmerged U4 builds with the old numbering must be
recreated. SurfaceVersion becomes 2.25.0-experimental. Main's admin password
account creation now requires the system.users.create action like the other
U4 account writes.
Refs: Backend main c76d8bd, PR #209

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-18 12:50:32 +00:00

123 lines
4.6 KiB
Go

package store
import (
"strings"
"testing"
)
func TestRolePolicyMigrationSupportsOneRoleForManyUsers(t *testing.T) {
st := openTestStore(t)
if err := st.CreateAccount("alice", "Alice", "hash", false); err != nil {
t.Fatal(err)
}
if err := st.CreateAccount("bob", "Bob", "hash", false); err != nil {
t.Fatal(err)
}
var aliceID, bobID string
if err := st.db.QueryRow(`SELECT id FROM users WHERE owner='alice'`).Scan(&aliceID); err != nil {
t.Fatal(err)
}
if err := st.db.QueryRow(`SELECT id FROM users WHERE owner='bob'`).Scan(&bobID); err != nil {
t.Fatal(err)
}
now := nowISO()
if _, err := st.db.Exec(st.q(`INSERT INTO roles(role_id,name,normalized_name,description,enabled,role_version,created_by,updated_by,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?)`), "role_engineer", "软件工程师", "软件工程师", "负责软件研发", true, 1, aliceID, aliceID, now, now); err != nil {
t.Fatal(err)
}
for _, binding := range []struct{ id, userID string }{{"bind_alice", aliceID}, {"bind_bob", bobID}} {
if _, err := st.db.Exec(st.q(`INSERT INTO user_role_bindings(binding_id,user_id,role_id,active,binding_version,assigned_by,assigned_at,ended_at)
VALUES(?,?,?,?,?,?,?,?)`), binding.id, binding.userID, "role_engineer", true, 1, aliceID, now, ""); err != nil {
t.Fatalf("assign shared role to %s: %v", binding.userID, err)
}
}
var count int
if err := st.db.QueryRow(`SELECT COUNT(*) FROM user_role_bindings WHERE role_id='role_engineer' AND active=TRUE`).Scan(&count); err != nil || count != 2 {
t.Fatalf("active role bindings=%d err=%v", count, err)
}
}
func TestRolePolicyMigrationRejectsTwoActiveRolesForOneUser(t *testing.T) {
st := openTestStore(t)
if err := st.CreateAccount("alice", "Alice", "hash", false); err != nil {
t.Fatal(err)
}
var userID string
if err := st.db.QueryRow(`SELECT id FROM users WHERE owner='alice'`).Scan(&userID); err != nil {
t.Fatal(err)
}
now := nowISO()
for _, roleID := range []string{"role_engineer", "role_reviewer"} {
if _, err := st.db.Exec(st.q(`INSERT INTO roles(role_id,name,normalized_name,description,enabled,role_version,created_by,updated_by,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?)`), roleID, roleID, roleID, "", true, 1, userID, userID, now, now); err != nil {
t.Fatal(err)
}
}
if _, err := st.db.Exec(st.q(`INSERT INTO user_role_bindings(binding_id,user_id,role_id,active,binding_version,assigned_by,assigned_at,ended_at)
VALUES(?,?,?,?,?,?,?,?)`), "bind_1", userID, "role_engineer", true, 1, userID, now, ""); err != nil {
t.Fatal(err)
}
_, err := st.db.Exec(st.q(`INSERT INTO user_role_bindings(binding_id,user_id,role_id,active,binding_version,assigned_by,assigned_at,ended_at)
VALUES(?,?,?,?,?,?,?,?)`), "bind_2", userID, "role_reviewer", true, 1, userID, now, "")
if err == nil || !strings.Contains(strings.ToLower(err.Error()), "unique") {
t.Fatalf("second active role must hit unique constraint: %v", err)
}
}
func TestRolePolicyMigrationCanRollbackAndReapply(t *testing.T) {
st := openTestStore(t)
if err := st.MigrateDown(48); err != nil {
t.Fatal(err)
}
if _, err := st.db.Exec(`SELECT role_id FROM roles LIMIT 1`); err == nil {
t.Fatal("roles table remained after v49 rollback")
}
if err := st.migrate(); err != nil {
t.Fatal(err)
}
if _, err := st.db.Exec(`SELECT role_id FROM roles LIMIT 1`); err != nil {
t.Fatalf("roles table missing after reapply: %v", err)
}
}
func TestRoleAuthorityImportStageUpgradesExistingV49(t *testing.T) {
st := openTestStore(t)
if err := st.MigrateDown(49); err != nil {
t.Fatal(err)
}
if _, err := st.db.Exec(`SELECT role_id FROM roles LIMIT 1`); err != nil {
t.Fatalf("v49 role authority was removed: %v", err)
}
if _, err := st.db.Exec(`SELECT state FROM role_authority_import_state WHERE id=1`); err == nil {
t.Fatal("v50 staging table remained after rollback to v49")
}
if err := st.migrate(); err != nil {
t.Fatal(err)
}
var state string
if err := st.db.QueryRow(`SELECT state FROM role_authority_import_state WHERE id=1`).Scan(&state); err != nil || state != "idle" {
t.Fatalf("v49 to v50 state=%q err=%v", state, err)
}
}
func TestRolePolicyMigrationSeedsLegacyAdministratorGrants(t *testing.T) {
st := openTestStore(t)
if err := st.MigrateDown(48); err != nil {
t.Fatal(err)
}
if err := st.CreateAccount("admin", "Admin", "hash", true); err != nil {
t.Fatal(err)
}
if err := st.migrate(); err != nil {
t.Fatal(err)
}
var grants int
if err := st.db.QueryRow(`SELECT COUNT(*) FROM system_grants g JOIN users u ON u.id=g.user_id
WHERE u.owner='admin' AND g.allowed=TRUE`).Scan(&grants); err != nil {
t.Fatal(err)
}
if grants != 12 {
t.Fatalf("legacy administrator grants=%d", grants)
}
}