package handler_test import ( "encoding/json" "fmt" "net/http" "testing" ) func TestRoleHandler_CreateRole(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "role-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "role-bootstrap-secret", "roleadmin", "roleadmin@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin failed") } if ok := registerUser(server.URL, "roleuser", "roleuser@test.com", "UserPass123!"); !ok { t.Fatal("register user failed") } userToken := getToken(server.URL, "roleuser", "UserPass123!") if userToken == "" { t.Fatal("get user token failed") } tests := []struct { name string payload map[string]interface{} token string wantStatus int }{ { name: "success", payload: map[string]interface{}{ "name": "Test Role", "code": "test_role_create", }, token: adminToken, wantStatus: http.StatusCreated, }, { name: "unauthorized", payload: map[string]interface{}{ "name": "Test Role Unauth", "code": "test_role_unauth", }, token: "", wantStatus: http.StatusUnauthorized, }, { name: "forbidden", payload: map[string]interface{}{ "name": "Test Role Forbidden", "code": "test_role_forbidden", }, token: userToken, wantStatus: http.StatusForbidden, }, { name: "missing_required_fields", payload: map[string]interface{}{"name": "Missing Code"}, token: adminToken, wantStatus: http.StatusBadRequest, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resp, body := doPost(server.URL+"/api/v1/roles", tt.token, tt.payload) defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { t.Errorf("expected status %d, got %d, body: %s", tt.wantStatus, resp.StatusCode, body) } }) } } func TestRoleHandler_ListRoles(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "role-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "role-bootstrap-secret", "roleadmin", "roleadmin@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin failed") } if ok := registerUser(server.URL, "roleuser", "roleuser@test.com", "UserPass123!"); !ok { t.Fatal("register user failed") } userToken := getToken(server.URL, "roleuser", "UserPass123!") if userToken == "" { t.Fatal("get user token failed") } tests := []struct { name string token string wantStatus int }{ { name: "success_admin", token: adminToken, wantStatus: http.StatusOK, }, { name: "forbidden_regular_user", token: userToken, wantStatus: http.StatusForbidden, }, { name: "unauthorized", token: "", wantStatus: http.StatusUnauthorized, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resp, body := doGet(server.URL+"/api/v1/roles", tt.token) defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { t.Errorf("expected status %d, got %d, body: %s", tt.wantStatus, resp.StatusCode, body) } }) } } func TestRoleHandler_GetRole(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "role-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "role-bootstrap-secret", "roleadmin", "roleadmin@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin failed") } // Create a role to retrieve createResp, createBody := doPost(server.URL+"/api/v1/roles", adminToken, map[string]interface{}{ "name": "Get Role Test", "code": "test_role_get", }) defer createResp.Body.Close() if createResp.StatusCode != http.StatusCreated { t.Fatalf("create role failed: %d %s", createResp.StatusCode, createBody) } var createResult map[string]interface{} if err := json.Unmarshal([]byte(createBody), &createResult); err != nil { t.Fatalf("parse create response failed: %v", err) } roleData := createResult["data"].(map[string]interface{}) roleID := int64(roleData["id"].(float64)) tests := []struct { name string roleID string token string wantStatus int }{ { name: "success", roleID: fmt.Sprintf("%d", roleID), token: adminToken, wantStatus: http.StatusOK, }, { name: "not_found", roleID: "99999", token: adminToken, wantStatus: http.StatusNotFound, }, { name: "invalid_id", roleID: "invalid", token: adminToken, wantStatus: http.StatusBadRequest, }, { name: "unauthorized", roleID: fmt.Sprintf("%d", roleID), token: "", wantStatus: http.StatusUnauthorized, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resp, body := doGet(server.URL+"/api/v1/roles/"+tt.roleID, tt.token) defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { t.Errorf("expected status %d, got %d, body: %s", tt.wantStatus, resp.StatusCode, body) } }) } } func TestRoleHandler_UpdateRole(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "role-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "role-bootstrap-secret", "roleadmin", "roleadmin@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin failed") } // Create a role to update createResp, createBody := doPost(server.URL+"/api/v1/roles", adminToken, map[string]interface{}{ "name": "Update Role Test", "code": "test_role_update", }) defer createResp.Body.Close() if createResp.StatusCode != http.StatusCreated { t.Fatalf("create role failed: %d %s", createResp.StatusCode, createBody) } var createResult map[string]interface{} if err := json.Unmarshal([]byte(createBody), &createResult); err != nil { t.Fatalf("parse create response failed: %v", err) } roleData := createResult["data"].(map[string]interface{}) roleID := int64(roleData["id"].(float64)) tests := []struct { name string roleID string payload map[string]interface{} token string wantStatus int }{ { name: "success", roleID: fmt.Sprintf("%d", roleID), payload: map[string]interface{}{ "name": "Updated Role Name", }, token: adminToken, wantStatus: http.StatusOK, }, { name: "invalid_id", roleID: "invalid", payload: map[string]interface{}{ "name": "Updated Role Name", }, token: adminToken, wantStatus: http.StatusBadRequest, }, { name: "unauthorized", roleID: fmt.Sprintf("%d", roleID), payload: map[string]interface{}{ "name": "Updated Role Name", }, token: "", wantStatus: http.StatusUnauthorized, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resp, body := doPut(server.URL+"/api/v1/roles/"+tt.roleID, tt.token, tt.payload) defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { t.Errorf("expected status %d, got %d, body: %s", tt.wantStatus, resp.StatusCode, body) } }) } } func TestRoleHandler_DeleteRole(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "role-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "role-bootstrap-secret", "roleadmin", "roleadmin@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin failed") } // Create a role to delete createResp, createBody := doPost(server.URL+"/api/v1/roles", adminToken, map[string]interface{}{ "name": "Delete Role Test", "code": "test_role_delete", }) defer createResp.Body.Close() if createResp.StatusCode != http.StatusCreated { t.Fatalf("create role failed: %d %s", createResp.StatusCode, createBody) } var createResult map[string]interface{} if err := json.Unmarshal([]byte(createBody), &createResult); err != nil { t.Fatalf("parse create response failed: %v", err) } roleData := createResult["data"].(map[string]interface{}) roleID := int64(roleData["id"].(float64)) tests := []struct { name string roleID string token string wantStatus int }{ { name: "success", roleID: fmt.Sprintf("%d", roleID), token: adminToken, wantStatus: http.StatusOK, }, { name: "invalid_id", roleID: "invalid", token: adminToken, wantStatus: http.StatusBadRequest, }, { name: "unauthorized", roleID: fmt.Sprintf("%d", roleID), token: "", wantStatus: http.StatusUnauthorized, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resp, body := doDelete(server.URL+"/api/v1/roles/"+tt.roleID, tt.token) defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { t.Errorf("expected status %d, got %d, body: %s", tt.wantStatus, resp.StatusCode, body) } }) } } func TestRoleHandler_UpdateRoleStatus(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "role-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "role-bootstrap-secret", "roleadmin", "roleadmin@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin failed") } // Create a role createResp, createBody := doPost(server.URL+"/api/v1/roles", adminToken, map[string]interface{}{ "name": "Status Role Test", "code": "test_role_status", }) defer createResp.Body.Close() if createResp.StatusCode != http.StatusCreated { t.Fatalf("create role failed: %d %s", createResp.StatusCode, createBody) } var createResult map[string]interface{} if err := json.Unmarshal([]byte(createBody), &createResult); err != nil { t.Fatalf("parse create response failed: %v", err) } roleData := createResult["data"].(map[string]interface{}) roleID := int64(roleData["id"].(float64)) tests := []struct { name string roleID string payload map[string]interface{} token string wantStatus int }{ { name: "success_disabled", roleID: fmt.Sprintf("%d", roleID), payload: map[string]interface{}{ "status": "disabled", }, token: adminToken, wantStatus: http.StatusOK, }, { name: "success_enabled", roleID: fmt.Sprintf("%d", roleID), payload: map[string]interface{}{ "status": "enabled", }, token: adminToken, wantStatus: http.StatusOK, }, { name: "invalid_status", roleID: fmt.Sprintf("%d", roleID), payload: map[string]interface{}{ "status": "invalid_status", }, token: adminToken, wantStatus: http.StatusBadRequest, }, { name: "invalid_id", roleID: "invalid", payload: map[string]interface{}{ "status": "disabled", }, token: adminToken, wantStatus: http.StatusBadRequest, }, { name: "unauthorized", roleID: fmt.Sprintf("%d", roleID), payload: map[string]interface{}{ "status": "disabled", }, token: "", wantStatus: http.StatusUnauthorized, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resp, body := doPut(server.URL+"/api/v1/roles/"+tt.roleID+"/status", tt.token, tt.payload) defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { t.Errorf("expected status %d, got %d, body: %s", tt.wantStatus, resp.StatusCode, body) } }) } } func TestRoleHandler_GetRolePermissions(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "role-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "role-bootstrap-secret", "roleadmin", "roleadmin@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin failed") } // Use the admin role (id=1) for testing resp, body := doGet(server.URL+"/api/v1/roles/1/permissions", adminToken) defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Fatalf("expected status %d, got %d, body: %s", http.StatusOK, resp.StatusCode, body) } var result map[string]interface{} if err := json.Unmarshal([]byte(body), &result); err != nil { t.Fatalf("parse response failed: %v", err) } if result["code"] != float64(0) { t.Errorf("expected code 0, got %v", result["code"]) } if result["data"] == nil { t.Errorf("expected data in response") } } func TestRoleHandler_AssignPermissions(t *testing.T) { server, cleanup := setupHandlerTestServer(t) defer cleanup() t.Setenv("BOOTSTRAP_SECRET", "role-bootstrap-secret") adminToken := bootstrapAdmin(server.URL, "role-bootstrap-secret", "roleadmin", "roleadmin@test.com", "AdminPass123!") if adminToken == "" { t.Fatal("bootstrap admin failed") } // Create a role createResp, createBody := doPost(server.URL+"/api/v1/roles", adminToken, map[string]interface{}{ "name": "Assign Perm Role Test", "code": "test_role_assign_perm", }) defer createResp.Body.Close() if createResp.StatusCode != http.StatusCreated { t.Fatalf("create role failed: %d %s", createResp.StatusCode, createBody) } var createResult map[string]interface{} if err := json.Unmarshal([]byte(createBody), &createResult); err != nil { t.Fatalf("parse create response failed: %v", err) } roleData := createResult["data"].(map[string]interface{}) roleID := int64(roleData["id"].(float64)) tests := []struct { name string roleID string payload map[string]interface{} token string wantStatus int }{ { name: "success", roleID: fmt.Sprintf("%d", roleID), payload: map[string]interface{}{ "permission_ids": []int64{1, 2}, }, token: adminToken, wantStatus: http.StatusOK, }, { name: "invalid_id", roleID: "invalid", payload: map[string]interface{}{ "permission_ids": []int64{1}, }, token: adminToken, wantStatus: http.StatusBadRequest, }, { name: "unauthorized", roleID: fmt.Sprintf("%d", roleID), payload: map[string]interface{}{ "permission_ids": []int64{1}, }, token: "", wantStatus: http.StatusUnauthorized, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resp, body := doPut(server.URL+"/api/v1/roles/"+tt.roleID+"/permissions", tt.token, tt.payload) defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { t.Errorf("expected status %d, got %d, body: %s", tt.wantStatus, resp.StatusCode, body) } }) } }