Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
UPDATE groups
SET system_managed = FALSE
WHERE id IN (
'00000000-0000-5000-8000-000000000005',
'00000000-0000-5000-8000-000000000006'
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Lock the seeded Standard Users and Read-Only Users groups (issue #168).
--
-- Owner decision 2026-10-16: these default groups are system-managed, so the
-- #1737 guard (ErrSystemManagedGroup) refuses API edits and deletes, like
-- Administrators, Purchaser and RI Exchanger. Locking is by id, so a group an
-- admin already renamed or edited is locked as-is; permissions are untouched.
UPDATE groups
SET system_managed = TRUE
WHERE id IN (
'00000000-0000-5000-8000-000000000005', -- Standard Users (000057)
'00000000-0000-5000-8000-000000000006' -- Read-Only Users (000057)
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//go:build integration

package migrations_test

import (
"context"
"testing"

"github.com/LeanerCloud/cloud-commitments-platform/internal/database/postgres/migrations"
"github.com/LeanerCloud/cloud-commitments-platform/internal/database/postgres/testhelpers"
"github.com/stretchr/testify/require"
)

func TestMigration_LockDefaultUserGroups(t *testing.T) {
ctx := context.Background()
container := testhelpers.RequirePostgresContainer(ctx, t)
t.Cleanup(func() { require.NoError(t, container.Cleanup(context.Background())) })
pool, path := container.DB.Pool(), getMigrationsPath()
ids := []string{
"00000000-0000-5000-8000-000000000005", // Standard Users
"00000000-0000-5000-8000-000000000006", // Read-Only Users
}
const otherID = "00000000-0000-5000-8000-000000000007" // Purchaser, already locked by its own seed

locked := func(id string) bool {
var v bool
require.NoError(t, pool.QueryRow(ctx, `SELECT system_managed FROM groups WHERE id=$1`, id).Scan(&v))
return v
}

require.NoError(t, migrations.MigrateToVersion(ctx, pool, path, 124))
for _, id := range ids {
require.False(t, locked(id), "before 000125 %s must be editable", id)
}
// An admin-renamed group is still locked by id, with its permissions kept.
_, err := pool.Exec(ctx, `UPDATE groups SET name='Renamed Standard' WHERE id=$1`, ids[0])
require.NoError(t, err)
var permsBefore string
require.NoError(t, pool.QueryRow(ctx, `SELECT permissions::text FROM groups WHERE id=$1`, ids[0]).Scan(&permsBefore))

require.NoError(t, migrations.MigrateToVersion(ctx, pool, path, 125))
for _, id := range ids {
require.True(t, locked(id), "after 000125 %s must be system-managed", id)
}
var name, permsAfter string
require.NoError(t, pool.QueryRow(ctx, `SELECT name, permissions::text FROM groups WHERE id=$1`, ids[0]).Scan(&name, &permsAfter))
require.Equal(t, "Renamed Standard", name)
require.Equal(t, permsBefore, permsAfter)
require.True(t, locked(otherID), "Purchaser must stay locked")

require.NoError(t, migrations.MigrateToVersion(ctx, pool, path, 124))
for _, id := range ids {
require.False(t, locked(id), "down must restore %s", id)
}
require.True(t, locked(otherID), "down must not unlock groups it did not lock")
}
Loading