package svc import ( "context" "net/http" "testing" "github.com/qsfera/server/pkg/middleware" settingsmsg "github.com/qsfera/server/protogen/gen/qsfera/messages/settings/v0" v0 "github.com/qsfera/server/protogen/gen/qsfera/services/settings/v0" "github.com/qsfera/server/services/settings/pkg/settings/mocks" "github.com/qsfera/server/services/settings/pkg/store/defaults" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" merrors "go-micro.dev/v4/errors" "go-micro.dev/v4/metadata" ) var ( ctxWithUUID = metadata.Set(context.Background(), middleware.AccountID, "61445573-4dbe-4d56-88dc-88ab47aceba7") ctxWithEmptyUUID = metadata.Set(context.Background(), middleware.AccountID, "") emptyCtx = context.Background() scenarios = []struct { name string accountUUID string ctx context.Context expect string }{ { name: "context with UUID; identifier = 'me'", ctx: ctxWithUUID, accountUUID: "me", expect: "61445573-4dbe-4d56-88dc-88ab47aceba7", }, { name: "context with empty UUID; identifier = 'me'", ctx: ctxWithEmptyUUID, accountUUID: "me", expect: "", }, { name: "context without UUID; identifier = 'me'", ctx: emptyCtx, accountUUID: "me", expect: "", }, { name: "context with UUID; identifier not 'me'", ctx: ctxWithUUID, accountUUID: "", expect: "", }, } ) func TestGetValidatedAccountUUID(t *testing.T) { for _, s := range scenarios { scenario := s t.Run(scenario.name, func(t *testing.T) { got := getValidatedAccountUUID(scenario.ctx, scenario.accountUUID) assert.NotPanics(t, func() { getValidatedAccountUUID(emptyCtx, scenario.accountUUID) }) assert.Equal(t, scenario.expect, got) }) } } func TestEditOwnRoleAssignment(t *testing.T) { manager := &mocks.Manager{} svc := Service{ manager: manager, } a := []*settingsmsg.UserRoleAssignment{} manager.On("ListRoleAssignments", mock.Anything).Return(a, nil) manager.On("WriteRoleAssignment", mock.Anything, mock.Anything).Return(nil, nil) // Creating an initial self assignment is expected to succeed for UserRole when no assignment exists yet req := v0.AssignRoleToUserRequest{ AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7", RoleId: defaults.BundleUUIDRoleUser, } res := v0.AssignRoleToUserResponse{} err := svc.AssignRoleToUser(ctxWithUUID, &req, &res) assert.Nil(t, err) // Creating an initial self assignment is expected to succeed for UserLightRole when no assignment exists yet req = v0.AssignRoleToUserRequest{ AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7", RoleId: defaults.BundleUUIDRoleUserLight, } res = v0.AssignRoleToUserResponse{} err = svc.AssignRoleToUser(ctxWithUUID, &req, &res) assert.Nil(t, err) // Creating an initial self assignment is expected to fail for non UserRole when no assignment exists yet req = v0.AssignRoleToUserRequest{ AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7", RoleId: defaults.BundleUUIDRoleAdmin, } res = v0.AssignRoleToUserResponse{} err = svc.AssignRoleToUser(ctxWithUUID, &req, &res) assert.NotNil(t, err) manager = &mocks.Manager{} svc = Service{ manager: manager, } a = []*settingsmsg.UserRoleAssignment{ { Id: "00000000-0000-0000-0000-000000000001", AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7", RoleId: "aceb15b8-7486-479f-ae32-c91118e07a39", }, } editRolePermission := &settingsmsg.Permission{ Operation: settingsmsg.Permission_OPERATION_READWRITE, Constraint: settingsmsg.Permission_CONSTRAINT_ALL, } manager.On("ListRoleAssignments", mock.Anything).Return(a, nil) manager.On("ReadPermissionByID", mock.Anything, mock.Anything).Return(editRolePermission, nil) // Creating an self assignment is expect to fail if there is already an assingment req = v0.AssignRoleToUserRequest{ AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7", RoleId: defaults.BundleUUIDRoleUser, } res = v0.AssignRoleToUserResponse{} err = svc.AssignRoleToUser(ctxWithUUID, &req, &res) assert.NotNil(t, err) manager.On("WriteRoleAssignment", mock.Anything, mock.Anything).Return(nil, nil) // Creating an assignment for somebody else is expected to succeed, give the right permissions req = v0.AssignRoleToUserRequest{ AccountUuid: "00000000-0000-0000-0000-000000000000", RoleId: "aceb15b8-7486-479f-ae32-c91118e07a39", } res = v0.AssignRoleToUserResponse{} err = svc.AssignRoleToUser(ctxWithUUID, &req, &res) assert.Nil(t, err) } func TestRemoveOwnRoleAssignment(t *testing.T) { manager := &mocks.Manager{} a := []*settingsmsg.UserRoleAssignment{ { Id: "00000000-0000-0000-0000-000000000001", AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7", RoleId: "aceb15b8-7486-479f-ae32-c91118e07a39", }, } editRolePermission := &settingsmsg.Permission{ Operation: settingsmsg.Permission_OPERATION_READWRITE, Constraint: settingsmsg.Permission_CONSTRAINT_ALL, } manager.On("ReadPermissionByID", mock.Anything, mock.Anything).Return(editRolePermission, nil) manager.On("ListRoleAssignments", mock.Anything).Return(a, nil) svc := Service{ manager: manager, } // Removing a role for oneself is expected to fail req := v0.RemoveRoleFromUserRequest{ Id: "00000000-0000-0000-0000-000000000001", } err := svc.RemoveRoleFromUser(ctxWithUUID, &req, nil) assert.NotNil(t, err) manager = &mocks.Manager{} manager.On("ListRoleAssignments", mock.Anything).Return(nil, nil) manager.On("RemoveRoleAssignment", mock.Anything).Return(nil) manager.On("ReadPermissionByID", mock.Anything, mock.Anything).Return(editRolePermission, nil) svc = Service{ manager: manager, } // Removing a role for someone else is expected to fail req = v0.RemoveRoleFromUserRequest{ Id: "00000000-0000-0000-0000-000000000002", } err = svc.RemoveRoleFromUser(ctxWithUUID, &req, nil) assert.Nil(t, err) } func TestListPermissionsOfCurrentUser(t *testing.T) { manager := &mocks.Manager{} a := []*settingsmsg.UserRoleAssignment{ { Id: "00000000-0000-0000-0000-000000000001", AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7", RoleId: "aceb15b8-7486-479f-ae32-c91118e07a39", }, } manager.On("ListRoleAssignments", mock.Anything).Return(a, nil) b := &settingsmsg.Bundle{ Id: "aceb15b8-7486-479f-ae32-c91118e07a39", Settings: []*settingsmsg.Setting{ { Name: "some-permission", }, { Name: "other-permission", }, { Name: "duplicate-permission", }, { Name: "duplicate-permission", }, }, } manager.On("ReadBundle", mock.Anything).Return(b, nil) svc := Service{ manager: manager, } // Listing permissions for yourself req := v0.ListPermissionsRequest{ AccountUuid: "61445573-4dbe-4d56-88dc-88ab47aceba7", } res := v0.ListPermissionsResponse{} err := svc.ListPermissions(ctxWithUUID, &req, &res) assert.NoError(t, err) assert.Len(t, res.Permissions, 3) } func TestListPermissionsOfOtherUser(t *testing.T) { manager := &mocks.Manager{} svc := Service{ manager: manager, } // Listing permissions for another user produces a not found error req := v0.ListPermissionsRequest{ AccountUuid: "66666666-4444-4444-8888-88ab47aceba7", } res := v0.ListPermissionsResponse{} err := svc.ListPermissions(ctxWithUUID, &req, &res) assert.Error(t, err) // assert the requested account uuid was not found merr, ok := merrors.As(err) assert.True(t, ok) assert.Equal(t, int32(http.StatusNotFound), merr.Code) assert.Contains(t, err.Error(), req.AccountUuid) } func TestListRoleAssignmentsFilteredValidation(t *testing.T) { manager := &mocks.Manager{} svc := Service{ manager: manager, } tests := map[string]struct { req *v0.ListRoleAssignmentsFilteredRequest statusCode int32 }{ "no filters": { req: &v0.ListRoleAssignmentsFilteredRequest{}, statusCode: http.StatusBadRequest, }, "multiple filters": { req: &v0.ListRoleAssignmentsFilteredRequest{ Filters: []*settingsmsg.UserRoleAssignmentFilter{ { Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT, Term: &settingsmsg.UserRoleAssignmentFilter_AccountUuid{ AccountUuid: "uid", }, }, { Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE, Term: &settingsmsg.UserRoleAssignmentFilter_RoleId{ RoleId: "rid", }, }, }, }, statusCode: http.StatusBadRequest, }, "bad filtertype": { req: &v0.ListRoleAssignmentsFilteredRequest{ Filters: []*settingsmsg.UserRoleAssignmentFilter{ { Type: settingsmsg.UserRoleAssignmentFilter_TYPE_UNKNOWN, }, }, }, statusCode: http.StatusBadRequest, }, "account filter without term": { req: &v0.ListRoleAssignmentsFilteredRequest{ Filters: []*settingsmsg.UserRoleAssignmentFilter{ { Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT, }, }, }, statusCode: http.StatusBadRequest, }, "account filter with invalid term": { req: &v0.ListRoleAssignmentsFilteredRequest{ Filters: []*settingsmsg.UserRoleAssignmentFilter{ { Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT, Term: &settingsmsg.UserRoleAssignmentFilter_AccountUuid{ AccountUuid: "invalid-&*&^%$#", }, }, }, }, statusCode: http.StatusBadRequest, }, "role filter without term": { req: &v0.ListRoleAssignmentsFilteredRequest{ Filters: []*settingsmsg.UserRoleAssignmentFilter{ { Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE, }, }, }, statusCode: http.StatusBadRequest, }, "role filter with invalid uuid": { req: &v0.ListRoleAssignmentsFilteredRequest{ Filters: []*settingsmsg.UserRoleAssignmentFilter{ { Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE, Term: &settingsmsg.UserRoleAssignmentFilter_RoleId{ RoleId: "this is no uuid", }, }, }, }, statusCode: http.StatusBadRequest, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { res := v0.ListRoleAssignmentsResponse{} err := svc.ListRoleAssignmentsFiltered(ctxWithUUID, test.req, &res) merr, ok := merrors.As(err) assert.True(t, ok) assert.Equal(t, int32(test.statusCode), merr.Code) }) } } func TestListRoleAssignmentsFilteredByAccount(t *testing.T) { accountUUID := "61445573-4dbe-4d56-88dc-88ab47aceba7" tests := map[string]struct { result []*settingsmsg.UserRoleAssignment err error status int32 }{ "handles manager error": { result: nil, err: assert.AnError, status: http.StatusNotFound, }, "succeeds with results": { result: []*settingsmsg.UserRoleAssignment{ { Id: "00000000-0000-0000-0000-000000000001", AccountUuid: accountUUID, RoleId: "aceb15b8-7486-479f-ae32-c91118e07a39", }, }, err: nil, status: http.StatusOK, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { manager := &mocks.Manager{} svc := Service{ manager: manager, } manager.On("ListRoleAssignments", mock.Anything).Return(test.result, test.err) req := &v0.ListRoleAssignmentsFilteredRequest{ Filters: []*settingsmsg.UserRoleAssignmentFilter{ { Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ACCOUNT, Term: &settingsmsg.UserRoleAssignmentFilter_AccountUuid{ AccountUuid: accountUUID, }, }, }, } res := v0.ListRoleAssignmentsResponse{} err := svc.ListRoleAssignmentsFiltered(ctxWithUUID, req, &res) switch test.err { case nil: assert.Nil(t, err) default: merr, ok := merrors.As(err) assert.True(t, ok) assert.Equal(t, int32(test.status), merr.Code) } }) } } func TestListRoleAssignmentsFilteredByRole(t *testing.T) { roleID := "61445573-4dbe-4d56-88dc-88ab47aceba7" tests := map[string]struct { result []*settingsmsg.UserRoleAssignment err error status int32 }{ "handles manager error": { result: nil, err: assert.AnError, status: http.StatusNotFound, }, "succeeds with results": { result: []*settingsmsg.UserRoleAssignment{ { Id: "00000000-0000-0000-0000-000000000001", AccountUuid: "aceb15b8-7486-479f-ae32-c91118e07a39", RoleId: roleID, }, }, err: nil, status: http.StatusOK, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { manager := &mocks.Manager{} svc := Service{ manager: manager, } manager.On("ListRoleAssignmentsByRole", mock.Anything).Return(test.result, test.err) req := &v0.ListRoleAssignmentsFilteredRequest{ Filters: []*settingsmsg.UserRoleAssignmentFilter{ { Type: settingsmsg.UserRoleAssignmentFilter_TYPE_ROLE, Term: &settingsmsg.UserRoleAssignmentFilter_RoleId{ RoleId: roleID, }, }, }, } res := v0.ListRoleAssignmentsResponse{} err := svc.ListRoleAssignmentsFiltered(ctxWithUUID, req, &res) switch test.err { case nil: assert.Nil(t, err) default: merr, ok := merrors.As(err) assert.True(t, ok) assert.Equal(t, int32(test.status), merr.Code) } }) } }