Files
wehub-resource-sync 75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:11:07 +08:00

1349 lines
41 KiB
Go

package workspaces_controllers
import (
"fmt"
"net/http"
"strings"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
users_enums "databasus-backend/internal/features/users/enums"
users_testing "databasus-backend/internal/features/users/testing"
workspaces_dto "databasus-backend/internal/features/workspaces/dto"
workspaces_testing "databasus-backend/internal/features/workspaces/testing"
test_utils "databasus-backend/internal/util/testing"
)
// ListMembers Tests
func Test_GetWorkspaceMembers_PermissionsEnforced(t *testing.T) {
tests := []struct {
name string
workspaceRole *users_enums.WorkspaceRole
isGlobalAdmin bool
expectSuccess bool
expectedStatusCode int
}{
{
name: "workspace owner can view members",
workspaceRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace admin can view members",
workspaceRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleAdmin; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace member can view members",
workspaceRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace viewer can view members",
workspaceRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleViewer; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "non-member cannot view members",
workspaceRole: nil,
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "global admin can view members",
workspaceRole: nil,
isGlobalAdmin: true,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI(
"Test Workspace",
owner,
router,
)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
var testUserToken string
if tt.isGlobalAdmin {
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
testUserToken = admin.Token
} else if tt.workspaceRole != nil && *tt.workspaceRole == users_enums.WorkspaceRoleOwner {
testUserToken = owner.Token
} else if tt.workspaceRole != nil {
member := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
member,
*tt.workspaceRole,
router,
)
testUserToken = member.Token
} else {
nonMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
testUserToken = nonMember.Token
}
if tt.expectSuccess {
var response workspaces_dto.GetMembersResponseDTO
test_utils.MakeGetRequestAndUnmarshal(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+testUserToken,
tt.expectedStatusCode,
&response,
)
assert.GreaterOrEqual(t, len(response.Members), 1)
} else {
resp := test_utils.MakeGetRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+testUserToken,
tt.expectedStatusCode,
)
assert.Contains(
t,
string(resp.Body),
"insufficient permissions to view workspace members",
)
}
})
}
}
func Test_GetWorkspaceMembers_WithInvalidWorkspaceID_ReturnsBadRequest(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
user := users_testing.CreateTestUser(users_enums.UserRoleMember)
resp := test_utils.MakeGetRequest(
t,
router,
"/api/v1/workspaces/memberships/invalid-uuid/members",
"Bearer "+user.Token,
http.StatusBadRequest,
)
assert.Contains(t, string(resp.Body), "Invalid workspace ID")
}
// AddMember Tests
func Test_AddMemberToWorkspace_PermissionsEnforced(t *testing.T) {
tests := []struct {
name string
requesterRole *users_enums.WorkspaceRole
isGlobalAdmin bool
expectSuccess bool
expectedStatusCode int
}{
{
name: "workspace owner can add member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "global admin can add member",
requesterRole: nil,
isGlobalAdmin: true,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace admin can add member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleAdmin; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace member cannot add member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "workspace viewer cannot add member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleViewer; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
newMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI(
"Test Workspace",
owner,
router,
)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
var testUserToken string
if tt.isGlobalAdmin {
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
testUserToken = admin.Token
} else if tt.requesterRole != nil && *tt.requesterRole == users_enums.WorkspaceRoleOwner {
testUserToken = owner.Token
} else if tt.requesterRole != nil {
requester := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
requester,
*tt.requesterRole,
router,
)
testUserToken = requester.Token
}
request := workspaces_dto.AddMemberRequestDTO{
Email: newMember.Email,
Role: users_enums.WorkspaceRoleViewer,
}
if tt.expectSuccess {
var response workspaces_dto.AddMemberResponseDTO
test_utils.MakePostRequestAndUnmarshal(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+testUserToken,
request,
tt.expectedStatusCode,
&response,
)
assert.True(t, response.Status == workspaces_dto.AddStatusAdded)
} else {
resp := test_utils.MakePostRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+testUserToken,
request,
tt.expectedStatusCode,
)
assert.Contains(t, string(resp.Body), "insufficient permissions to manage members")
}
})
}
}
func Test_AddMemberToWorkspace_WhenUserIsAlreadyMember_ReturnsBadRequest(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
member := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI("Test Workspace", owner, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
member,
users_enums.WorkspaceRoleViewer,
router,
)
// Try to add the same user again
request := workspaces_dto.AddMemberRequestDTO{
Email: member.Email,
Role: users_enums.WorkspaceRoleViewer,
}
resp := test_utils.MakePostRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+owner.Token,
request,
http.StatusBadRequest,
)
assert.Contains(t, string(resp.Body), "user is already a member of this workspace")
}
func Test_AddMemberToWorkspace_WithNonExistentUser_ReturnsInvited(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI("Test Workspace", owner, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
request := workspaces_dto.AddMemberRequestDTO{
Email: uuid.New().String() + "@example.com", // Non-existent user
Role: users_enums.WorkspaceRoleViewer,
}
var response workspaces_dto.AddMemberResponseDTO
test_utils.MakePostRequestAndUnmarshal(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+owner.Token,
request,
http.StatusOK,
&response,
)
assert.True(t, response.Status == workspaces_dto.AddStatusInvited)
}
func Test_AddMemberToWorkspace_WhenWorkspaceAdminTriesToAddAdmin_ReturnsBadRequest(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaceAdmin := users_testing.CreateTestUser(users_enums.UserRoleMember)
newMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI("Test Workspace", owner, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
workspaceAdmin,
users_enums.WorkspaceRoleAdmin,
router,
)
// Workspace admin tries to add another admin (should fail)
request := workspaces_dto.AddMemberRequestDTO{
Email: newMember.Email,
Role: users_enums.WorkspaceRoleAdmin,
}
resp := test_utils.MakePostRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+workspaceAdmin.Token,
request,
http.StatusForbidden,
)
assert.Contains(t, string(resp.Body), "only workspace owner can add/manage admins")
}
func Test_AddMemberToWorkspace_WhenWorkspaceAdminTriesToAddWorkspaceAdmin_ReturnsBadRequest(
t *testing.T,
) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaceAdmin := users_testing.CreateTestUser(users_enums.UserRoleMember)
newMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI("Test Workspace", owner, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
workspaceAdmin,
users_enums.WorkspaceRoleAdmin,
router,
)
// WorkspaceAdmin tries to add another WorkspaceAdmin (should fail)
request := workspaces_dto.AddMemberRequestDTO{
Email: newMember.Email,
Role: users_enums.WorkspaceRoleAdmin,
}
resp := test_utils.MakePostRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+workspaceAdmin.Token,
request,
http.StatusForbidden,
)
assert.Contains(t, string(resp.Body), "only workspace owner can add/manage admins")
}
func Test_AddWorkspaceAdmin_PermissionsEnforced(t *testing.T) {
tests := []struct {
name string
requesterRole *users_enums.WorkspaceRole
isGlobalAdmin bool
expectSuccess bool
expectedStatusCode int
}{
{
name: "workspace owner can add workspace admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "global admin can add workspace admin",
requesterRole: nil,
isGlobalAdmin: true,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace admin cannot add workspace admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleAdmin; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "workspace member cannot add workspace admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "workspace viewer cannot add workspace admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleViewer; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
newMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI(
"Test Workspace",
owner,
router,
)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
var testUserToken string
if tt.isGlobalAdmin {
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
testUserToken = admin.Token
} else if tt.requesterRole != nil && *tt.requesterRole == users_enums.WorkspaceRoleOwner {
testUserToken = owner.Token
} else if tt.requesterRole != nil {
requester := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
requester,
*tt.requesterRole,
router,
)
testUserToken = requester.Token
}
request := workspaces_dto.AddMemberRequestDTO{
Email: newMember.Email,
Role: users_enums.WorkspaceRoleAdmin,
}
if tt.expectSuccess {
var response workspaces_dto.AddMemberResponseDTO
test_utils.MakePostRequestAndUnmarshal(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+testUserToken,
request,
tt.expectedStatusCode,
&response,
)
assert.True(t, response.Status == workspaces_dto.AddStatusAdded)
} else {
resp := test_utils.MakePostRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+testUserToken,
request,
tt.expectedStatusCode,
)
assert.Contains(t, string(resp.Body), "only workspace owner can add/manage admins")
}
})
}
}
func Test_InviteMemberToWorkspace_PermissionsEnforced(t *testing.T) {
tests := []struct {
name string
requesterRole *users_enums.WorkspaceRole
isGlobalAdmin bool
expectSuccess bool
expectedStatusCode int
}{
{
name: "workspace owner can invite member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "global admin can invite member",
requesterRole: nil,
isGlobalAdmin: true,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace admin can invite member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleAdmin; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace member cannot invite member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "workspace viewer cannot invite member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleViewer; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
users_testing.EnableMemberInvitations()
defer users_testing.ResetSettingsToDefaults()
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI(
"Test Workspace",
owner,
router,
)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
var testUserToken string
if tt.isGlobalAdmin {
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
testUserToken = admin.Token
} else if tt.requesterRole != nil && *tt.requesterRole == users_enums.WorkspaceRoleOwner {
testUserToken = owner.Token
} else if tt.requesterRole != nil {
requester := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
requester,
*tt.requesterRole,
router,
)
testUserToken = requester.Token
}
request := workspaces_dto.AddMemberRequestDTO{
Email: fmt.Sprintf("invite-%s@example.com", uuid.New().String()),
Role: users_enums.WorkspaceRoleViewer,
}
if tt.expectSuccess {
var response workspaces_dto.AddMemberResponseDTO
test_utils.MakePostRequestAndUnmarshal(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+testUserToken,
request,
tt.expectedStatusCode,
&response,
)
assert.True(t, response.Status == workspaces_dto.AddStatusInvited)
} else {
resp := test_utils.MakePostRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+testUserToken,
request,
tt.expectedStatusCode,
)
assert.Contains(t, string(resp.Body), "insufficient permissions to manage members")
}
})
}
}
// ChangeMemberRole Tests
func Test_ChangeMemberRole_PermissionsEnforced(t *testing.T) {
tests := []struct {
name string
requesterRole *users_enums.WorkspaceRole
isGlobalAdmin bool
expectSuccess bool
expectedStatusCode int
}{
{
name: "workspace owner can change member role",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace admin can change member role",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleAdmin; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "global admin can change member role",
requesterRole: nil,
isGlobalAdmin: true,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace member cannot change member role",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "workspace viewer cannot change member role",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleViewer; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
targetMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI(
"Test Workspace",
owner,
router,
)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
targetMember,
users_enums.WorkspaceRoleViewer,
router,
)
var testUserToken string
if tt.isGlobalAdmin {
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
testUserToken = admin.Token
} else if tt.requesterRole != nil && *tt.requesterRole == users_enums.WorkspaceRoleOwner {
testUserToken = owner.Token
} else if tt.requesterRole != nil {
requester := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
requester,
*tt.requesterRole,
router,
)
testUserToken = requester.Token
}
request := workspaces_dto.ChangeMemberRoleRequestDTO{
Role: users_enums.WorkspaceRoleViewer,
}
resp := test_utils.MakePutRequest(
t,
router,
fmt.Sprintf(
"/api/v1/workspaces/memberships/%s/members/%s/role",
workspace.ID.String(),
targetMember.UserID.String(),
),
"Bearer "+testUserToken,
request,
tt.expectedStatusCode,
)
if tt.expectSuccess {
assert.Contains(t, string(resp.Body), "Member role changed successfully")
} else {
assert.Contains(t, string(resp.Body), "insufficient permissions to manage members")
}
})
}
}
func Test_ChangeMemberRoleToAdmin_PermissionsEnforced(t *testing.T) {
tests := []struct {
name string
requesterRole *users_enums.WorkspaceRole
isGlobalAdmin bool
expectSuccess bool
expectedStatusCode int
}{
{
name: "workspace owner can promote to admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "global admin can promote to admin",
requesterRole: nil,
isGlobalAdmin: true,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace admin cannot promote to admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleAdmin; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "workspace member cannot promote to admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
targetMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI(
"Test Workspace",
owner,
router,
)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
targetMember,
users_enums.WorkspaceRoleViewer,
router,
)
var testUserToken string
if tt.isGlobalAdmin {
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
testUserToken = admin.Token
} else if tt.requesterRole != nil && *tt.requesterRole == users_enums.WorkspaceRoleOwner {
testUserToken = owner.Token
} else if tt.requesterRole != nil {
requester := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
requester,
*tt.requesterRole,
router,
)
testUserToken = requester.Token
}
request := workspaces_dto.ChangeMemberRoleRequestDTO{
Role: users_enums.WorkspaceRoleAdmin,
}
resp := test_utils.MakePutRequest(
t,
router,
fmt.Sprintf(
"/api/v1/workspaces/memberships/%s/members/%s/role",
workspace.ID.String(),
targetMember.UserID.String(),
),
"Bearer "+testUserToken,
request,
tt.expectedStatusCode,
)
if tt.expectSuccess {
assert.Contains(t, string(resp.Body), "Member role changed successfully")
} else {
assert.Contains(t, string(resp.Body), "only workspace owner can add/manage admins")
}
})
}
}
func Test_ChangeMemberRole_WhenChangingOwnRole_ReturnsBadRequest(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI("Test Workspace", owner, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
request := workspaces_dto.ChangeMemberRoleRequestDTO{
Role: users_enums.WorkspaceRoleMember,
}
resp := test_utils.MakePutRequest(
t,
router,
fmt.Sprintf(
"/api/v1/workspaces/memberships/%s/members/%s/role",
workspace.ID.String(),
owner.UserID.String(),
),
"Bearer "+owner.Token,
request,
http.StatusBadRequest,
)
assert.Contains(t, string(resp.Body), "cannot change your own role")
}
func Test_ChangeMemberRole_WhenChangingOwnerRole_ReturnsBadRequest(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI("Test Workspace", owner, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
request := workspaces_dto.ChangeMemberRoleRequestDTO{
Role: users_enums.WorkspaceRoleMember,
}
resp := test_utils.MakePutRequest(
t,
router,
fmt.Sprintf(
"/api/v1/workspaces/memberships/%s/members/%s/role",
workspace.ID.String(),
owner.UserID.String(),
),
"Bearer "+admin.Token,
request,
http.StatusBadRequest,
)
assert.Contains(t, string(resp.Body), "cannot change owner role")
}
// RemoveMember Tests
func Test_RemoveMemberFromWorkspace_PermissionsEnforced(t *testing.T) {
tests := []struct {
name string
requesterRole *users_enums.WorkspaceRole
isGlobalAdmin bool
expectSuccess bool
expectedStatusCode int
}{
{
name: "workspace owner can remove member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "global admin can remove member",
requesterRole: nil,
isGlobalAdmin: true,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace admin can remove member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleAdmin; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace member cannot remove member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "workspace viewer cannot remove member",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleViewer; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
targetMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI(
"Test Workspace",
owner,
router,
)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
targetMember,
users_enums.WorkspaceRoleViewer,
router,
)
var testUserToken string
if tt.isGlobalAdmin {
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
testUserToken = admin.Token
} else if tt.requesterRole != nil && *tt.requesterRole == users_enums.WorkspaceRoleOwner {
testUserToken = owner.Token
} else if tt.requesterRole != nil {
requester := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
requester,
*tt.requesterRole,
router,
)
testUserToken = requester.Token
}
resp := test_utils.MakeRequest(t, router, test_utils.RequestOptions{
Method: "DELETE",
URL: fmt.Sprintf(
"/api/v1/workspaces/memberships/%s/members/%s",
workspace.ID.String(),
targetMember.UserID.String(),
),
AuthToken: "Bearer " + testUserToken,
ExpectedStatus: tt.expectedStatusCode,
})
if tt.expectSuccess {
assert.Contains(t, string(resp.Body), "Member removed successfully")
} else {
assert.Contains(t, string(resp.Body), "insufficient permissions to remove members")
}
})
}
}
func Test_RemoveMemberFromWorkspace_WhenRemovingOwner_ReturnsBadRequest(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI("Test Workspace", owner, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
resp := test_utils.MakeRequest(t, router, test_utils.RequestOptions{
Method: "DELETE",
URL: fmt.Sprintf(
"/api/v1/workspaces/memberships/%s/members/%s",
workspace.ID.String(),
owner.UserID.String(),
),
AuthToken: "Bearer " + admin.Token,
ExpectedStatus: http.StatusBadRequest,
})
assert.Contains(t, string(resp.Body), "cannot remove workspace owner, transfer ownership first")
}
func Test_RemoveWorkspaceAdmin_PermissionsEnforced(t *testing.T) {
tests := []struct {
name string
requesterRole *users_enums.WorkspaceRole
isGlobalAdmin bool
expectSuccess bool
expectedStatusCode int
}{
{
name: "workspace owner can remove workspace admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "global admin can remove workspace admin",
requesterRole: nil,
isGlobalAdmin: true,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace admin cannot remove workspace admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleAdmin; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "workspace member cannot remove workspace admin",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
targetAdmin := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI(
"Test Workspace",
owner,
router,
)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
targetAdmin,
users_enums.WorkspaceRoleAdmin,
router,
)
var testUserToken string
if tt.isGlobalAdmin {
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
testUserToken = admin.Token
} else if tt.requesterRole != nil && *tt.requesterRole == users_enums.WorkspaceRoleOwner {
testUserToken = owner.Token
} else if tt.requesterRole != nil {
requester := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
requester,
*tt.requesterRole,
router,
)
testUserToken = requester.Token
}
resp := test_utils.MakeRequest(t, router, test_utils.RequestOptions{
Method: "DELETE",
URL: fmt.Sprintf(
"/api/v1/workspaces/memberships/%s/members/%s",
workspace.ID.String(),
targetAdmin.UserID.String(),
),
AuthToken: "Bearer " + testUserToken,
ExpectedStatus: tt.expectedStatusCode,
})
if tt.expectSuccess {
assert.Contains(t, string(resp.Body), "Member removed successfully")
} else {
// Workspace admins get specific error about removing admins
// Members/viewers get generic insufficient permissions error
body := string(resp.Body)
assert.True(t,
strings.Contains(body, "only workspace owner can remove admins") ||
strings.Contains(body, "insufficient permissions to remove members"),
"Expected permission error, got: %s", body)
}
})
}
}
// TransferOwnership Tests
func Test_TransferWorkspaceOwnership_PermissionsEnforced(t *testing.T) {
tests := []struct {
name string
requesterRole *users_enums.WorkspaceRole
isGlobalAdmin bool
expectSuccess bool
expectedStatusCode int
}{
{
name: "workspace owner can transfer ownership",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleOwner; return &r }(),
isGlobalAdmin: false,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "global admin can transfer ownership",
requesterRole: nil,
isGlobalAdmin: true,
expectSuccess: true,
expectedStatusCode: http.StatusOK,
},
{
name: "workspace member cannot transfer ownership",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleViewer; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
{
name: "workspace admin cannot transfer ownership",
requesterRole: func() *users_enums.WorkspaceRole { r := users_enums.WorkspaceRoleMember; return &r }(),
isGlobalAdmin: false,
expectSuccess: false,
expectedStatusCode: http.StatusForbidden,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
newOwner := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI(
"Test Workspace",
owner,
router,
)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
newOwner,
users_enums.WorkspaceRoleMember,
router,
)
var testUserToken string
if tt.isGlobalAdmin {
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
testUserToken = admin.Token
} else if tt.requesterRole != nil && *tt.requesterRole == users_enums.WorkspaceRoleOwner {
testUserToken = owner.Token
} else if tt.requesterRole != nil {
requester := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
requester,
*tt.requesterRole,
router,
)
testUserToken = requester.Token
}
request := workspaces_dto.TransferOwnershipRequestDTO{
NewOwnerEmail: newOwner.Email,
}
resp := test_utils.MakePostRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/transfer-ownership",
"Bearer "+testUserToken,
request,
tt.expectedStatusCode,
)
if tt.expectSuccess {
assert.Contains(t, string(resp.Body), "Ownership transferred successfully")
} else {
assert.Contains(
t,
string(resp.Body),
"only workspace owner or admin can transfer ownership",
)
}
})
}
}
func Test_TransferWorkspaceOwnership_WhenNewOwnerIsNotMember_ReturnsBadRequest(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
nonMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI("Test Workspace", owner, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
request := workspaces_dto.TransferOwnershipRequestDTO{
NewOwnerEmail: nonMember.Email,
}
resp := test_utils.MakePostRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/transfer-ownership",
"Bearer "+owner.Token,
request,
http.StatusBadRequest,
)
assert.Contains(t, string(resp.Body), "new owner must be a workspace member")
}
func Test_TransferWorkspaceOwnership_ThereIsOnlyOneOwner_OldOwnerBecomeAdmin(t *testing.T) {
router := workspaces_testing.CreateTestRouter(
GetWorkspaceController(),
GetMembershipController(),
)
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
member := users_testing.CreateTestUser(users_enums.UserRoleMember)
workspace, _ := workspaces_testing.CreateTestWorkspaceViaAPI("Test Workspace", owner, router)
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
workspaces_testing.AddMemberToWorkspaceViaOwner(
workspace,
member,
users_enums.WorkspaceRoleMember,
router,
)
// Transfer ownership to the member
request := workspaces_dto.TransferOwnershipRequestDTO{
NewOwnerEmail: member.Email,
}
resp := test_utils.MakePostRequest(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/transfer-ownership",
"Bearer "+owner.Token,
request,
http.StatusOK,
)
assert.Contains(t, string(resp.Body), "Ownership transferred successfully")
// Get all members using the new owner's token
var membersResponse workspaces_dto.GetMembersResponseDTO
test_utils.MakeGetRequestAndUnmarshal(
t,
router,
"/api/v1/workspaces/memberships/"+workspace.ID.String()+"/members",
"Bearer "+member.Token,
http.StatusOK,
&membersResponse,
)
// Verify there is only one owner
ownerCount := 0
var currentOwner *workspaces_dto.WorkspaceMemberResponseDTO
for _, m := range membersResponse.Members {
if m.Role == users_enums.WorkspaceRoleOwner {
ownerCount++
currentOwner = &m
}
}
assert.Equal(t, 1, ownerCount, "There should be exactly one owner")
assert.NotNil(t, currentOwner, "Owner should exist")
assert.Equal(
t,
member.UserID,
currentOwner.UserID,
"The new owner should be the member we transferred to",
)
assert.Equal(
t,
member.Email,
currentOwner.Email,
"Owner email should match the transferred member",
)
// verify previous owner is now an admin
for _, m := range membersResponse.Members {
if m.UserID == owner.UserID {
assert.Equal(
t,
users_enums.WorkspaceRoleAdmin,
m.Role,
"Previous owner should now be admin",
)
break
}
}
}