Files
Authorization/handlers/authorize_test.go
T

467 lines
12 KiB
Go

package handlers
import (
"authorization/models"
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestInitAuthService(t *testing.T) {
// Test that InitAuthService can be called
// It may panic if DB is not available, which is expected behavior
defer func() {
if r := recover(); r != nil {
t.Logf("InitAuthService panicked (expected without DB): %v", r)
// This is acceptable - the function requires a DB connection
}
}()
// This will initialize with whatever DB is available
// If DB is nil, it will panic which is caught above
InitAuthService()
t.Log("InitAuthService completed successfully")
}
func TestAuthorizeHandlerNoJWTClaims(t *testing.T) {
// Setup
req := httptest.NewRequest("POST", AuthCheckEndpoint, nil)
w := httptest.NewRecorder()
// Execute
AuthorizeHandler(w, req)
// Assert
if w.Code != http.StatusUnauthorized {
t.Errorf(ExpectedStatusMessage, http.StatusUnauthorized, w.Code)
}
}
func TestAuthorizeHandlerInvalidJSON(t *testing.T) {
// Setup - no need to init service, we're testing JSON parsing before auth
claims := &models.Claims{
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBufferString("invalid json"))
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
// Execute
AuthorizeHandler(w, req)
// Assert
if w.Code != http.StatusBadRequest {
t.Errorf(ExpectedStatusMessage, http.StatusBadRequest, w.Code)
}
}
func TestAuthorizeHandlerMissingRequiredFields(t *testing.T) {
testCases := []struct {
name string
payload models.AuthorizationContext
}{
{
name: "Missing UserID",
payload: models.AuthorizationContext{Resource: "document", Action: "read"},
},
{
name: "Missing Resource",
payload: models.AuthorizationContext{UsersID: "user123", Action: "read"},
},
{
name: "Missing Action",
payload: models.AuthorizationContext{UsersID: "user123", Resource: "document"},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
claims := &models.Claims{
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
body, _ := json.Marshal(tc.payload)
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body))
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
AuthorizeHandler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf(ExpectedStatusMessage, http.StatusBadRequest, w.Code)
}
})
}
}
func TestAuthorizeHandlerUserIDMismatch(t *testing.T) {
// Setup
claims := &models.Claims{
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UsersID: "differentUser",
Resource: "document",
Action: "read",
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body))
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
// Execute
AuthorizeHandler(w, req)
// Assert
if w.Code != http.StatusForbidden {
t.Errorf(ExpectedStatusMessage, http.StatusForbidden, w.Code)
}
}
func TestAuthorizeHandlerNilMaps(t *testing.T) {
// Test that nil maps don't cause additional panics beyond missing authService
claims := &models.Claims{
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UsersID: "user123",
Resource: "document",
Action: "read",
ResourceData: nil, // nil map
Environment: nil, // nil map
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body))
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
// Execute - may panic if authService is nil (which is expected without DB)
defer func() {
if r := recover(); r != nil {
t.Logf("Handler panicked (expected without authService): %v", r)
}
}()
AuthorizeHandler(w, req)
// Verify handler set a response code if it didn't panic
if w.Code != 0 {
t.Logf("Handler completed with status code: %d", w.Code)
}
}
// Additional comprehensive test cases
func TestAuthorizeHandlerEmptyUserID(t *testing.T) {
claims := &models.Claims{
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UsersID: "",
Resource: "document",
Action: "read",
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body))
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
AuthorizeHandler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("Expected status %d for empty UserID, got %d", http.StatusBadRequest, w.Code)
}
}
func TestAuthorizeHandlerEmptyResource(t *testing.T) {
claims := &models.Claims{
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UsersID: "user123",
Resource: "",
Action: "read",
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body))
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
AuthorizeHandler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("Expected status %d for empty Resource, got %d", http.StatusBadRequest, w.Code)
}
}
func TestAuthorizeHandlerEmptyAction(t *testing.T) {
claims := &models.Claims{
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UsersID: "user123",
Resource: "document",
Action: "",
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body))
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
AuthorizeHandler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("Expected status %d for empty Action, got %d", http.StatusBadRequest, w.Code)
}
}
func TestAuthorizeHandlerInvalidClaimsType(t *testing.T) {
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBufferString(`{"userId":"user123","resource":"doc","action":"read"}`))
// Set claims as wrong type
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), "invalid_claims_type")
req = req.WithContext(ctx)
w := httptest.NewRecorder()
AuthorizeHandler(w, req)
if w.Code != http.StatusUnauthorized {
t.Errorf("Expected status %d for invalid claims type, got %d", http.StatusUnauthorized, w.Code)
}
}
func TestAuthorizeHandlerMalformedJSON(t *testing.T) {
claims := &models.Claims{
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
testCases := []struct {
name string
payload string
}{
{"Incomplete JSON", `{"userId":"user123","resource":"doc"`},
{"Invalid quotes", `{userId:"user123"}`},
{"Trailing comma", `{"userId":"user123",}`},
{"Just whitespace", ` `},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBufferString(tc.payload))
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
AuthorizeHandler(w, req)
if w.Code != http.StatusBadRequest {
t.Errorf("Expected status %d for malformed JSON, got %d", http.StatusBadRequest, w.Code)
}
})
}
}
func TestAuthorizeHandlerSpecialCharactersInFields(t *testing.T) {
testCases := []struct {
name string
userID string
resource string
action string
}{
{"Special chars in resource", "user123", "document/file-name_v1.2", "read"},
{"Unicode in resource", "user123", "文档", "read"},
{"Spaces in action", "user123", "document", "read write"},
{"Special chars in userID", "user-123_test", "document", "read"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
payload := models.AuthorizationContext{
UsersID: tc.userID,
Resource: tc.resource,
Action: tc.action,
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body))
// Update claims to match userID
testClaims := &models.Claims{
UsersID: tc.userID,
RoleID: models.RoleIDs{1},
}
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), testClaims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
// May panic if authService is nil (expected without DB)
defer func() {
if r := recover(); r != nil {
t.Logf("Handler panicked (expected without authService): %v", r)
}
}()
AuthorizeHandler(w, req)
// If it didn't panic, verify it set a response status
if w.Code != 0 {
t.Logf("Handler completed with status: %d", w.Code)
}
})
}
}
func TestAuthorizeHandlerWithResourceData(t *testing.T) {
// Test that ResourceData is properly passed through to authorization
claims := &models.Claims{
UsersID: "user123",
RoleID: models.RoleIDs{1},
}
payload := models.AuthorizationContext{
UsersID: "user123",
Resource: "personnel",
Action: "assign_role",
ResourceData: map[string]string{
"region": "01",
},
}
body, _ := json.Marshal(payload)
req := httptest.NewRequest("POST", AuthCheckEndpoint, bytes.NewBuffer(body))
ctx := context.WithValue(req.Context(), models.ContextKey("claims"), claims)
req = req.WithContext(ctx)
w := httptest.NewRecorder()
defer func() {
if r := recover(); r != nil {
t.Logf("Handler panicked (expected without DB): %v", r)
}
}()
AuthorizeHandler(w, req)
// ResourceData should not cause any parsing errors
if w.Code == http.StatusBadRequest {
t.Errorf("Handler returned bad request with valid ResourceData")
}
}
func TestCollectClaimRolesIncludesProjectRoles(t *testing.T) {
claims := &models.Claims{
RoleID: models.RoleIDs{2},
Projects: []models.ProjectClaim{
{ProjectID: 7, RoleID: models.RoleIDs{2, 4}},
{ProjectID: 8, RoleID: models.RoleIDs{5}},
},
}
roles := collectClaimRoles(claims)
if len(roles) != 3 {
t.Fatalf("expected 3 unique roles, got %d (%v)", len(roles), roles)
}
if roles[0] != 2 || roles[1] != 4 || roles[2] != 5 {
t.Fatalf("unexpected role order/content: %v", roles)
}
}
func TestIntersectRolesReturnsOverlap(t *testing.T) {
requested := []int{4, 9, 4, 2}
available := []int{2, 4, 5}
result := intersectRoles(requested, available)
if len(result) != 2 {
t.Fatalf("expected 2 matching roles, got %d (%v)", len(result), result)
}
if result[0] != 4 || result[1] != 2 {
t.Fatalf("unexpected intersection result: %v", result)
}
}
func TestCollectRequestedRolesFromArray(t *testing.T) {
ctx := &models.AuthorizationContext{
RoleIDs: []int{3, 7},
RoleID: 3,
}
result := collectRequestedRoles(ctx)
if len(result) != 2 {
t.Fatalf("expected 2 requested roles, got %d (%v)", len(result), result)
}
if result[0] != 3 || result[1] != 7 {
t.Fatalf("unexpected requested roles: %v", result)
}
}
func TestCollectClaimRolesIncludesAdditionalRoles(t *testing.T) {
claims := &models.Claims{
RoleID: models.RoleIDs{30},
AdditionalRoleID: models.RoleIDs{4, 5, 30},
}
roles := collectClaimRoles(claims)
if len(roles) != 3 {
t.Fatalf("expected 3 unique roles, got %d (%v)", len(roles), roles)
}
if roles[0] != 30 || roles[1] != 4 || roles[2] != 5 {
t.Fatalf("unexpected role order/content: %v", roles)
}
}
func TestBuildRoleCandidates_PrioritizesRequestedThenFallsBackToClaims(t *testing.T) {
requested := []int{30}
claimRoles := []int{30, 44, 52}
result := buildRoleCandidates(requested, claimRoles)
if len(result) != 3 {
t.Fatalf("expected 3 candidate roles, got %d (%v)", len(result), result)
}
if result[0] != 30 || result[1] != 44 || result[2] != 52 {
t.Fatalf("unexpected candidate role order/content: %v", result)
}
}
func TestBuildRoleCandidates_ReturnsNilWhenRequestedNotInClaims(t *testing.T) {
requested := []int{999}
claimRoles := []int{30, 44}
result := buildRoleCandidates(requested, claimRoles)
if len(result) != 0 {
t.Fatalf("expected no candidates for mismatched requested role, got %v", result)
}
}