236 lines
8.1 KiB
Go
236 lines
8.1 KiB
Go
package repository
|
|
|
|
import (
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"testing"
|
|
)
|
|
|
|
func TestDefaultPermissionSeedUsesFinancePermissions(t *testing.T) {
|
|
permissions := map[string]struct{}{}
|
|
for _, permission := range defaultPermissions {
|
|
permissions[permission.Code] = struct{}{}
|
|
}
|
|
|
|
for _, code := range []string{"money:view", "money:payment-link:create"} {
|
|
if _, ok := permissions[code]; ok {
|
|
t.Fatalf("legacy money permission %s must not be part of default seed", code)
|
|
}
|
|
}
|
|
|
|
for _, code := range []string{
|
|
"payment-temporary-link:create",
|
|
"finance:view",
|
|
"finance-withdrawal:view",
|
|
"host-withdrawal:view",
|
|
} {
|
|
if _, ok := permissions[code]; !ok {
|
|
t.Fatalf("finance permission %s missing from default seed", code)
|
|
}
|
|
}
|
|
for _, code := range []string{
|
|
"finance-application:create",
|
|
"finance-application:audit",
|
|
"finance-operation:user-coin-credit",
|
|
"finance-operation:user-coin-debit",
|
|
"finance-operation:user-wallet-credit",
|
|
"finance-operation:user-wallet-debit",
|
|
"finance-operation:coin-seller-coin-credit",
|
|
"finance-operation:coin-seller-coin-debit",
|
|
} {
|
|
if _, ok := permissions[code]; ok {
|
|
t.Fatalf("removed finance application permission %s must not be seeded", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDefaultRoleFinancePermissionTemplates(t *testing.T) {
|
|
opsPermissions := seedTestPermissionSet(defaultRolePermissionCodes(roleCodeOperationsLead))
|
|
|
|
for _, code := range []string{
|
|
"payment-bill:export",
|
|
"host-withdrawal:view",
|
|
} {
|
|
if _, ok := opsPermissions[code]; !ok {
|
|
t.Fatalf("ops-admin default permissions missing %s", code)
|
|
}
|
|
}
|
|
|
|
for _, code := range []string{
|
|
"finance:view",
|
|
"payment-temporary-link:create",
|
|
"payment-third-party:update-rate",
|
|
"payment-third-party:sync-rates",
|
|
"finance-application:create",
|
|
"finance-application:audit",
|
|
"finance-operation:user-coin-credit",
|
|
"finance-operation:user-coin-debit",
|
|
"finance-operation:user-wallet-credit",
|
|
"finance-operation:user-wallet-debit",
|
|
"finance-operation:coin-seller-coin-credit",
|
|
"finance-operation:coin-seller-coin-debit",
|
|
} {
|
|
if _, ok := opsPermissions[code]; ok {
|
|
t.Fatalf("ops-admin must not receive removed permission %s", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestManagedDefaultRolePermissionMatrix(t *testing.T) {
|
|
if len(managedDefaultRoles) != 7 {
|
|
t.Fatalf("managed role count = %d, want 7", len(managedDefaultRoles))
|
|
}
|
|
roleNames := map[string]string{}
|
|
for _, role := range managedDefaultRoles {
|
|
if previous, ok := roleNames[role.Code]; ok {
|
|
t.Fatalf("duplicate role code %s for %s and %s", role.Code, previous, role.Name)
|
|
}
|
|
roleNames[role.Code] = role.Name
|
|
}
|
|
wantNames := map[string]string{
|
|
roleCodePlatformAdmin: "超级管理员",
|
|
roleCodeOperationsLead: "运营负责人",
|
|
roleCodeOperationsSpecialist: "运营专员",
|
|
roleCodeProductLead: "产品负责人",
|
|
roleCodeProductSpecialist: "产品专员",
|
|
roleCodeFinanceLead: "财务负责人",
|
|
roleCodeFinanceSpecialist: "财务专员",
|
|
}
|
|
if !reflect.DeepEqual(roleNames, wantNames) {
|
|
t.Fatalf("managed roles = %#v, want %#v", roleNames, wantNames)
|
|
}
|
|
|
|
defined := map[string]struct{}{}
|
|
for _, permission := range defaultPermissions {
|
|
defined[permission.Code] = struct{}{}
|
|
}
|
|
for _, role := range managedDefaultRoles {
|
|
if role.Code == roleCodePlatformAdmin {
|
|
continue
|
|
}
|
|
seen := map[string]struct{}{}
|
|
for _, code := range defaultRolePermissionCodes(role.Code) {
|
|
if _, ok := seen[code]; ok {
|
|
t.Fatalf("role %s contains duplicate permission %s", role.Code, code)
|
|
}
|
|
seen[code] = struct{}{}
|
|
if _, ok := defined[code]; !ok {
|
|
t.Fatalf("role %s references permission %s missing from defaultPermissions", role.Code, code)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestManagedDefaultRoleModuleBoundaries(t *testing.T) {
|
|
assertRolePermissions(t, roleCodeOperationsLead,
|
|
[]string{"app-config:update", "payment-third-party:sync-methods", "game-catalog:update", "country:update"},
|
|
[]string{"app-version:view", "finance:view", "payment-third-party:update-rate", "role:view"},
|
|
)
|
|
assertRolePermissions(t, roleCodeOperationsSpecialist,
|
|
[]string{"room:update", "game-robot:update", "room-rps-config:update", "payment-bill:export"},
|
|
[]string{"activity:update", "daily-task:update", "game-catalog:update", "self-game:update", "coin-adjustment:create"},
|
|
)
|
|
assertRolePermissions(t, roleCodeProductLead,
|
|
[]string{"app-version:update", "daily-task:update", "game-catalog:update", "resource:update"},
|
|
[]string{"bd:create", "coin-adjustment:create", "country:update", "finance:view"},
|
|
)
|
|
assertRolePermissions(t, roleCodeProductSpecialist,
|
|
[]string{"resource:update", "room-robot:update", "game-catalog:view", "daily-task:view"},
|
|
[]string{"app-config:update", "room:update", "game-catalog:update", "daily-task:update", "bd:view"},
|
|
)
|
|
|
|
financeLead := defaultRolePermissionCodes(roleCodeFinanceLead)
|
|
financeSpecialist := defaultRolePermissionCodes(roleCodeFinanceSpecialist)
|
|
if !reflect.DeepEqual(financeLead, financeSpecialist) {
|
|
t.Fatalf("finance lead and specialist permissions differ: lead=%v specialist=%v", financeLead, financeSpecialist)
|
|
}
|
|
assertRolePermissions(t, roleCodeFinanceLead,
|
|
[]string{"finance:view", "payment-bill:view", "payment-bill:export", "finance-withdrawal:audit", "room-rps-order:view"},
|
|
[]string{"game-catalog:view", "room-rps-config:view", "app-user:view", "daily-task:view"},
|
|
)
|
|
}
|
|
|
|
func TestRolePermissionMigrationMatchesManagedMatrix(t *testing.T) {
|
|
content, err := os.ReadFile("../../migrations/093_role_permission_matrix.sql")
|
|
if err != nil {
|
|
t.Fatalf("read role permission migration: %v", err)
|
|
}
|
|
sqlText := string(content)
|
|
for _, roleCode := range []string{
|
|
roleCodeOperationsLead,
|
|
roleCodeOperationsSpecialist,
|
|
roleCodeProductLead,
|
|
roleCodeProductSpecialist,
|
|
} {
|
|
pattern := regexp.MustCompile(`(?s)WHERE role\.code = '` + regexp.QuoteMeta(roleCode) + `'\s+AND permission\.code IN \((.*?)\);`)
|
|
matches := pattern.FindStringSubmatch(sqlText)
|
|
if len(matches) != 2 {
|
|
t.Fatalf("migration permission block missing for %s", roleCode)
|
|
}
|
|
assertMigrationPermissionCodes(t, roleCode, matches[1])
|
|
}
|
|
|
|
financePattern := regexp.MustCompile(`(?s)WHERE role\.code IN \('finance-lead', 'finance-specialist'\)\s+AND permission\.code IN \((.*?)\);`)
|
|
financeMatches := financePattern.FindStringSubmatch(sqlText)
|
|
if len(financeMatches) != 2 {
|
|
t.Fatal("migration permission block missing for finance roles")
|
|
}
|
|
assertMigrationPermissionCodes(t, roleCodeFinanceLead, financeMatches[1])
|
|
assertMigrationPermissionCodes(t, roleCodeFinanceSpecialist, financeMatches[1])
|
|
}
|
|
|
|
func assertMigrationPermissionCodes(t *testing.T, roleCode string, sqlList string) {
|
|
t.Helper()
|
|
quotedCode := regexp.MustCompile(`'([a-z0-9:-]+)'`)
|
|
matches := quotedCode.FindAllStringSubmatch(sqlList, -1)
|
|
actual := make([]string, 0, len(matches))
|
|
for _, match := range matches {
|
|
actual = append(actual, match[1])
|
|
}
|
|
expected := append([]string(nil), defaultRolePermissionCodes(roleCode)...)
|
|
sort.Strings(actual)
|
|
sort.Strings(expected)
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("migration permissions for %s differ\nactual-only=%v\nexpected-only=%v", roleCode, stringSetDifference(actual, expected), stringSetDifference(expected, actual))
|
|
}
|
|
}
|
|
|
|
func stringSetDifference(left []string, right []string) []string {
|
|
rightSet := make(map[string]struct{}, len(right))
|
|
for _, item := range right {
|
|
rightSet[item] = struct{}{}
|
|
}
|
|
out := make([]string, 0)
|
|
for _, item := range left {
|
|
if _, ok := rightSet[item]; !ok {
|
|
out = append(out, item)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func assertRolePermissions(t *testing.T, roleCode string, required []string, forbidden []string) {
|
|
t.Helper()
|
|
permissions := seedTestPermissionSet(defaultRolePermissionCodes(roleCode))
|
|
for _, code := range required {
|
|
if _, ok := permissions[code]; !ok {
|
|
t.Errorf("role %s missing required permission %s", roleCode, code)
|
|
}
|
|
}
|
|
for _, code := range forbidden {
|
|
if _, ok := permissions[code]; ok {
|
|
t.Errorf("role %s must not have permission %s", roleCode, code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func seedTestPermissionSet(codes []string) map[string]struct{} {
|
|
out := make(map[string]struct{}, len(codes))
|
|
for _, code := range codes {
|
|
out[code] = struct{}{}
|
|
}
|
|
return out
|
|
}
|