access_test.go
package access_test
import (
"testing"
"github.com/readysite/readysite/pkg/database"
"github.com/readysite/readysite/pkg/database/engines"
"github.com/readysite/readysite/website/internal/access"
"github.com/readysite/readysite/website/models"
)
func init() {
// Use in-memory database for tests
models.DB = engines.NewMemory()
models.Users = database.Manage(models.DB, new(models.User),
database.WithUniqueIndex[models.User]("Email"),
)
models.ACLRules = database.Manage(models.DB, new(models.ACLRule),
database.WithIndex[models.ACLRule]("SubjectType", "SubjectID"),
database.WithIndex[models.ACLRule]("ResourceType", "ResourceID"),
)
}
func TestIsAdmin(t *testing.T) {
adminUser := &models.User{Role: access.RoleAdmin}
normalUser := &models.User{Role: access.RoleUser}
viewerUser := &models.User{Role: access.RoleViewer}
if !access.IsAdmin(adminUser) {
t.Error("expected admin user to be admin")
}
if access.IsAdmin(normalUser) {
t.Error("expected normal user not to be admin")
}
if access.IsAdmin(viewerUser) {
t.Error("expected viewer user not to be admin")
}
}
func TestGrants(t *testing.T) {
tests := []struct {
have, want string
expected bool
}{
// Admin grants everything
{access.PermAdmin, access.PermAdmin, true},
{access.PermAdmin, access.PermDelete, true},
{access.PermAdmin, access.PermWrite, true},
{access.PermAdmin, access.PermRead, true},
// Delete grants write and read
{access.PermDelete, access.PermDelete, true},
{access.PermDelete, access.PermWrite, true},
{access.PermDelete, access.PermRead, true},
{access.PermDelete, access.PermAdmin, false},
// Write grants read only
{access.PermWrite, access.PermWrite, true},
{access.PermWrite, access.PermRead, true},
{access.PermWrite, access.PermDelete, false},
{access.PermWrite, access.PermAdmin, false},
// Read grants only read
{access.PermRead, access.PermRead, true},
{access.PermRead, access.PermWrite, false},
{access.PermRead, access.PermDelete, false},
{access.PermRead, access.PermAdmin, false},
}
for _, tt := range tests {
result := access.Grants(tt.have, tt.want)
if result != tt.expected {
t.Errorf("Grants(%q, %q) = %v, want %v", tt.have, tt.want, result, tt.expected)
}
}
}
func TestCheckAccessAdmin(t *testing.T) {
adminUser := &models.User{Role: access.RoleAdmin}
// Admins should have access to everything
if !access.CheckAccess(adminUser, access.ResourcePage, "any-page", access.PermRead) {
t.Error("admin should have read access to pages")
}
if !access.CheckAccess(adminUser, access.ResourcePage, "any-page", access.PermAdmin) {
t.Error("admin should have admin access to pages")
}
}
func TestCheckAccessPublicRule(t *testing.T) {
// Create a public rule for a page
rule := &models.ACLRule{
SubjectType: access.SubjectPublic,
SubjectID: "",
ResourceType: access.ResourcePage,
ResourceID: "public-page",
Permission: access.PermRead,
}
models.ACLRules.Insert(rule)
defer models.ACLRules.Delete(rule)
// Anonymous user should have read access
if !access.CheckAccess(nil, access.ResourcePage, "public-page", access.PermRead) {
t.Error("anonymous user should have read access to public page")
}
// Anonymous user should NOT have write access
if access.CheckAccess(nil, access.ResourcePage, "public-page", access.PermWrite) {
t.Error("anonymous user should not have write access to public page")
}
}
func TestCheckAccessUserRule(t *testing.T) {
// Create a user
user := &models.User{Role: access.RoleUser}
user.ID = "test-user-acl"
models.Users.Insert(user)
defer models.Users.Delete(user)
// Create a user-specific rule
rule := &models.ACLRule{
SubjectType: access.SubjectUser,
SubjectID: user.ID,
ResourceType: access.ResourcePage,
ResourceID: "user-page",
Permission: access.PermWrite,
}
models.ACLRules.Insert(rule)
defer models.ACLRules.Delete(rule)
// User should have write access
if !access.CheckAccess(user, access.ResourcePage, "user-page", access.PermWrite) {
t.Error("user should have write access to their page")
}
// User should have read access (implied by write)
if !access.CheckAccess(user, access.ResourcePage, "user-page", access.PermRead) {
t.Error("user should have read access to their page")
}
// Other users should NOT have access
otherUser := &models.User{Role: access.RoleUser}
otherUser.ID = "other-user"
if access.CheckAccess(otherUser, access.ResourcePage, "user-page", access.PermWrite) {
t.Error("other user should not have access")
}
}
func TestCheckAccessRoleRule(t *testing.T) {
// Create a role-based rule
rule := &models.ACLRule{
SubjectType: access.SubjectRole,
SubjectID: access.RoleUser,
ResourceType: access.ResourceCollection,
ResourceID: "", // All collections
Permission: access.PermRead,
}
models.ACLRules.Insert(rule)
defer models.ACLRules.Delete(rule)
// User with 'user' role should have read access
user := &models.User{Role: access.RoleUser}
user.ID = "role-test-user"
if !access.CheckAccess(user, access.ResourceCollection, "any-collection", access.PermRead) {
t.Error("user should have read access via role")
}
// Viewer should NOT have access
viewer := &models.User{Role: access.RoleViewer}
viewer.ID = "role-test-viewer"
if access.CheckAccess(viewer, access.ResourceCollection, "any-collection", access.PermRead) {
t.Error("viewer should not have access via user role")
}
}
func TestGrantAndRevokeAccess(t *testing.T) {
// Grant access
err := access.GrantAccess(access.SubjectUser, "grant-test-user", access.ResourcePage, "grant-page", access.PermWrite)
if err != nil {
t.Fatalf("failed to grant access: %v", err)
}
// Verify rule was created
rules, err := access.GetRules(access.ResourcePage, "grant-page")
if err != nil {
t.Fatalf("failed to get rules: %v", err)
}
if len(rules) != 1 {
t.Errorf("expected 1 rule, got %d", len(rules))
}
// Revoke access
err = access.RevokeAccess(access.SubjectUser, "grant-test-user", access.ResourcePage, "grant-page")
if err != nil {
t.Fatalf("failed to revoke access: %v", err)
}
// Verify rule was deleted
rules, _ = access.GetRules(access.ResourcePage, "grant-page")
if len(rules) != 0 {
t.Errorf("expected 0 rules after revoke, got %d", len(rules))
}
}