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) } }