Feat v0.9.9 surface access roles (#83)
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-runners (push) Has been skipped
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-go-pg (push) Successful in 2m49s
CI/CD / test-sqlite (push) Successful in 2m59s
CI/CD / build-and-deploy (push) Successful in 1m28s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #83.
This commit is contained in:
2026-04-03 20:06:25 +00:00
committed by xcaliber
parent b0e9dd7f80
commit 414b2290ce
10 changed files with 360 additions and 7 deletions

View File

@@ -259,6 +259,8 @@ func validAccessLevels(access string) bool {
return true
case strings.HasPrefix(access, "group:"):
return strings.TrimPrefix(access, "group:") != ""
case strings.HasPrefix(access, "role:"):
return strings.TrimPrefix(access, "role:") != ""
default:
return false
}

View File

@@ -262,6 +262,93 @@ func TestRequireRole_Denied(t *testing.T) {
}
}
// ── Store: HasRoleInAnyTeam ──────────────────
func TestHasRoleInAnyTeam_PrimaryRole(t *testing.T) {
database.RequireTestDB(t)
stores := testStores(t)
ctx := context.Background()
_, userID, _ := seedTeamAndMember(t, stores)
// "member" is the primary role assigned during seedTeamAndMember
has, err := stores.Teams.HasRoleInAnyTeam(ctx, userID, "member")
if err != nil {
t.Fatalf("HasRoleInAnyTeam: %v", err)
}
if !has {
t.Error("expected true for primary role 'member'")
}
}
func TestHasRoleInAnyTeam_AdditionalRole(t *testing.T) {
database.RequireTestDB(t)
stores := testStores(t)
ctx := context.Background()
teamID, userID, _ := seedTeamAndMember(t, stores)
stores.Teams.AddUserRole(ctx, teamID, userID, "reviewer", userID)
has, err := stores.Teams.HasRoleInAnyTeam(ctx, userID, "reviewer")
if err != nil {
t.Fatalf("HasRoleInAnyTeam: %v", err)
}
if !has {
t.Error("expected true for additional role 'reviewer'")
}
}
func TestHasRoleInAnyTeam_NoMatch(t *testing.T) {
database.RequireTestDB(t)
stores := testStores(t)
ctx := context.Background()
_, userID, _ := seedTeamAndMember(t, stores)
has, err := stores.Teams.HasRoleInAnyTeam(ctx, userID, "nonexistent")
if err != nil {
t.Fatalf("HasRoleInAnyTeam: %v", err)
}
if has {
t.Error("expected false for non-existent role")
}
}
// ── Manifest: role access validation ────────
func TestValidateManifest_SurfaceRoleAccess(t *testing.T) {
m := map[string]any{
"id": "role-pkg",
"title": "Role Gated",
"type": "surface",
"surfaces": []any{
map[string]any{"path": "/", "access": "role:approver"},
},
}
info, err := ValidateManifest(m)
if err != nil {
t.Fatalf("unexpected error for role:approver access: %v", err)
}
if !info.HasSurfaces {
t.Error("expected HasSurfaces to be true")
}
}
func TestValidateManifest_SurfaceRoleAccessEmpty(t *testing.T) {
m := map[string]any{
"id": "role-pkg",
"title": "Role Gated",
"type": "surface",
"surfaces": []any{
map[string]any{"path": "/", "access": "role:"},
},
}
_, err := ValidateManifest(m)
if err == nil {
t.Fatal("expected error for empty role name 'role:'")
}
}
// ── helpers ──────────────────────────────────
func seedRoleUser(t *testing.T, username, email string) string {