Pulse/internal/api/rbac_handlers_test.go
rcourtman 6ed1fdf806 feat(rbac): implement RBAC UI, OIDC group mapping, and API standard auth
- Added Roles and Users settings panels
- Implemented OIDC group-to-role mappings in config and auth flow
- Standardized API token context handling via pkg/auth
- Added Pulse Pro branding and upgrade banners to RBAC features
- Cleanup: Removed empty code blocks and fixed lint errors
2026-01-09 19:16:34 +00:00

179 lines
4.9 KiB
Go

package api
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/pkg/auth"
)
type mockRBACManager struct {
roles []auth.Role
assignments []auth.UserRoleAssignment
}
func (m *mockRBACManager) GetRoles() []auth.Role { return m.roles }
func (m *mockRBACManager) GetRole(id string) (auth.Role, bool) {
for _, r := range m.roles {
if r.ID == id {
return r, true
}
}
return auth.Role{}, false
}
func (m *mockRBACManager) SaveRole(role auth.Role) error {
m.roles = append(m.roles, role)
return nil
}
func (m *mockRBACManager) DeleteRole(id string) error {
for i, r := range m.roles {
if r.ID == id {
m.roles = append(m.roles[:i], m.roles[i+1:]...)
return nil
}
}
return nil
}
func (m *mockRBACManager) GetUserAssignments() []auth.UserRoleAssignment { return m.assignments }
func (m *mockRBACManager) GetUserAssignment(username string) (auth.UserRoleAssignment, bool) {
for _, a := range m.assignments {
if a.Username == username {
return a, true
}
}
return auth.UserRoleAssignment{}, false
}
func (m *mockRBACManager) AssignRole(username string, roleID string) error { return nil }
func (m *mockRBACManager) UpdateUserRoles(username string, roleIDs []string) error {
m.assignments = append(m.assignments, auth.UserRoleAssignment{Username: username, RoleIDs: roleIDs})
return nil
}
func (m *mockRBACManager) RemoveRole(username string, roleID string) error { return nil }
func (m *mockRBACManager) GetUserPermissions(username string) []auth.Permission {
return []auth.Permission{{Action: "read", Resource: "nodes"}}
}
func TestHandleRoles(t *testing.T) {
cfg := &config.Config{}
h := NewRBACHandlers(cfg)
mock := &mockRBACManager{
roles: []auth.Role{{ID: "admin", Name: "Admin"}},
}
auth.SetManager(mock)
t.Run("List roles", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/admin/roles", nil)
rr := httptest.NewRecorder()
h.HandleRoles(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected status OK, got %d", rr.Code)
}
var roles []auth.Role
json.Unmarshal(rr.Body.Bytes(), &roles)
if len(roles) != 1 || roles[0].ID != "admin" {
t.Errorf("Unexpected roles: %+v", roles)
}
})
t.Run("Create role", func(t *testing.T) {
role := auth.Role{ID: "custom", Name: "Custom"}
body, _ := json.Marshal(role)
req := httptest.NewRequest("POST", "/api/admin/roles", bytes.NewReader(body))
rr := httptest.NewRecorder()
h.HandleRoles(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected status OK, got %d", rr.Code)
}
if len(mock.roles) != 2 || mock.roles[1].ID != "custom" {
t.Errorf("Role not saved correctly")
}
})
t.Run("POST with path ID (rejected)", func(t *testing.T) {
req := httptest.NewRequest("POST", "/api/admin/roles/new-role", nil)
rr := httptest.NewRecorder()
h.HandleRoles(rr, req)
if rr.Code != http.StatusBadRequest {
t.Errorf("Expected status Bad Request, got %d", rr.Code)
}
})
t.Run("Get role", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/admin/roles/admin", nil)
rr := httptest.NewRecorder()
h.HandleRoles(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected status OK, got %d", rr.Code)
}
var role auth.Role
json.Unmarshal(rr.Body.Bytes(), &role)
if role.ID != "admin" {
t.Errorf("Expected admin role, got %s", role.ID)
}
})
t.Run("Delete role", func(t *testing.T) {
req := httptest.NewRequest("DELETE", "/api/admin/roles/custom", nil)
rr := httptest.NewRecorder()
h.HandleRoles(rr, req)
if rr.Code != http.StatusNoContent {
t.Errorf("Expected status No Content, got %d", rr.Code)
}
if len(mock.roles) != 1 {
t.Errorf("Role not deleted")
}
})
}
func TestHandleUserRoleActions(t *testing.T) {
cfg := &config.Config{}
h := NewRBACHandlers(cfg)
mock := &mockRBACManager{}
auth.SetManager(mock)
t.Run("Update user roles", func(t *testing.T) {
reqData := struct {
RoleIDs []string `json:"roleIds"`
}{RoleIDs: []string{"admin", "viewer"}}
body, _ := json.Marshal(reqData)
req := httptest.NewRequest("PUT", "/api/admin/users/testuser/roles", bytes.NewReader(body))
rr := httptest.NewRecorder()
h.HandleUserRoleActions(rr, req)
if rr.Code != http.StatusNoContent {
t.Errorf("Expected status No Content, got %d", rr.Code)
}
if len(mock.assignments) != 1 || mock.assignments[0].Username != "testuser" {
t.Errorf("Assignment not saved correctly")
}
})
t.Run("Get effective permissions", func(t *testing.T) {
req := httptest.NewRequest("GET", "/api/admin/users/testuser/permissions", nil)
rr := httptest.NewRecorder()
h.HandleUserRoleActions(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Expected status OK, got %d", rr.Code)
}
var perms []auth.Permission
json.Unmarshal(rr.Body.Bytes(), &perms)
if len(perms) != 1 || perms[0].Action != "read" {
t.Errorf("Unexpected permissions: %+v", perms)
}
})
}