Skip to content

Commit ef60e47

Browse files
feat: TT-188 add and remove users from groups (#653)
* feat: TT-188 add & remove groups to user service * feat: TT-188 add ngrx flow & test * refactor: TT-188 refactor some names * refactor: TT-188 refactor 'removeTo' to 'removeFrom' references
1 parent 1ec9ce0 commit ef60e47

File tree

10 files changed

+340
-4
lines changed

10 files changed

+340
-4
lines changed

src/app/modules/user/store/user.effects.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class UserEffects {
1414
loadUserInfo$: Observable<Action> = this.actions$.pipe(
1515
ofType(actions.UserActionTypes.LOAD_USER),
1616
map((action: actions.LoadUser) => action.userId),
17-
mergeMap((userId) =>
17+
mergeMap((userId: string) =>
1818
this.userService.loadUser(userId).pipe(
1919
map((response) => new actions.LoadUserSuccess(response)),
2020
catchError((error) => of(new actions.LoadUserFail(error)))

src/app/modules/users/models/users.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export interface User {
22
name: string;
33
email: string;
44
roles?: string[];
5+
groups?: string[];
56
id: string;
67
tenant_id?: string;
78
deleted?: string;

src/app/modules/users/services/users.service.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,24 @@ describe('UsersService', () => {
5151
const grantRoleRequest = httpMock.expectOne(`${service.baseUrl}/${userId}/roles/${roleId}/revoke`);
5252
expect(grantRoleRequest.request.method).toBe('POST');
5353
});
54+
55+
it('add user to group', () => {
56+
const userId = 'userId';
57+
const group = 'admin';
58+
const addGroupURL = `${service.baseUrl}/${userId}/groups/add`;
59+
60+
service.addUserToGroup(userId, group).subscribe();
61+
62+
expect(httpMock.expectOne(addGroupURL).request.method).toBe('POST');
63+
});
64+
65+
it('remove user from group', () => {
66+
const userId = 'userId';
67+
const group = 'admin';
68+
const removeGroupURL = `${service.baseUrl}/${userId}/groups/remove`;
69+
70+
service.removeUserFromGroup(userId, group).subscribe();
71+
72+
expect(httpMock.expectOne(removeGroupURL).request.method).toBe('POST');
73+
});
5474
});

src/app/modules/users/services/users.service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { HttpClient } from '@angular/common/http';
22
import { Injectable } from '@angular/core';
33
import { Observable } from 'rxjs';
4+
import { User } from '../models/users';
45
import { environment } from './../../../../environments/environment';
56
@Injectable({
67
providedIn: 'root',
@@ -23,4 +24,16 @@ export class UsersService {
2324
const url = `${this.baseUrl}/${userId}/roles/${roleId}/revoke`;
2425
return this.http.post(url, null);
2526
}
27+
28+
addUserToGroup(userId: string, group: string): Observable<User> {
29+
return this.http.post<User>(`${this.baseUrl}/${userId}/groups/add`, {
30+
group_name: group,
31+
});
32+
}
33+
34+
removeUserFromGroup(userId: string, group: string): Observable<User> {
35+
return this.http.post<User>(`${this.baseUrl}/${userId}/groups/remove`, {
36+
group_name: group,
37+
});
38+
}
2639
}

src/app/modules/users/store/user.actions.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,46 @@ describe('UserActions', () => {
5252
const action = new actions.RevokeRoleUserFail('error');
5353
expect(action.type).toEqual(actions.UserActionTypes.REVOKE_USER_ROLE_FAIL);
5454
});
55+
56+
it('AddUserToGroup type is UserActionTypes.ADD_USER_TO_GROUP', () => {
57+
const userId = 'userId';
58+
const groupName = 'groupName';
59+
const action = new actions.AddUserToGroup(userId, groupName);
60+
61+
expect(action.type).toEqual(actions.UserActionTypes.ADD_USER_TO_GROUP);
62+
});
63+
64+
it('AddUserToGroupSuccess type is UserActionTypes.ADD_USER_TO_GROUP_SUCCESS', () => {
65+
const payload: User = { id: 'id', email: 'email', name: 'name' };
66+
const action = new actions.AddUserToGroupSuccess(payload);
67+
68+
expect(action.type).toEqual(actions.UserActionTypes.ADD_USER_TO_GROUP_SUCCESS);
69+
});
70+
71+
it('AddUserToGroupFail type is UserActionTypes.ADD_USER_TO_GROUP_FAIL', () => {
72+
const action = new actions.AddUserToGroupFail('error');
73+
74+
expect(action.type).toEqual(actions.UserActionTypes.ADD_USER_TO_GROUP_FAIL);
75+
});
76+
77+
it('RemoveUserFromGroup type is UserActionTypes.REMOVE_USER_FROM_GROUP', () => {
78+
const userId = 'userId';
79+
const groupName = 'groupName';
80+
const action = new actions.RemoveUserFromGroup(userId, groupName);
81+
82+
expect(action.type).toEqual(actions.UserActionTypes.REMOVE_USER_FROM_GROUP);
83+
});
84+
85+
it('RemoveUserFromGroupSuccess type is UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS', () => {
86+
const payload: User = { id: 'id', email: 'email', name: 'name' };
87+
const action = new actions.RemoveUserFromGroupSuccess(payload);
88+
89+
expect(action.type).toEqual(actions.UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS);
90+
});
91+
92+
it('RemoveUserFromGroupFail type is UserActionTypes.REMOVE_USER_FROM_GROUP_FAIL', () => {
93+
const action = new actions.RemoveUserFromGroupFail('error');
94+
95+
expect(action.type).toEqual(actions.UserActionTypes.REMOVE_USER_FROM_GROUP_FAIL);
96+
});
5597
});

src/app/modules/users/store/user.actions.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export enum UserActionTypes {
1111
REVOKE_USER_ROLE = '[User] REVOKE_USER_ROLE',
1212
REVOKE_USER_ROLE_SUCCESS = '[User] REVOKE_USER_ROLE_SUCCESS',
1313
REVOKE_USER_ROLE_FAIL = '[User] REVOKE_USER_ROLE_FAIL',
14+
ADD_USER_TO_GROUP = '[User] ADD_USER_TO_GROUP',
15+
ADD_USER_TO_GROUP_SUCCESS = '[User] ADD_USER_TO_GROUP_SUCCESS',
16+
ADD_USER_TO_GROUP_FAIL = '[User] ADD_USER_TO_GROUP_FAIL',
17+
REMOVE_USER_FROM_GROUP = '[User] REMOVE_USER_FROM_GROUP',
18+
REMOVE_USER_FROM_GROUP_SUCCESS = '[User] REMOVE_USER_FROM_GROUP_SUCCESS',
19+
REMOVE_USER_FROM_GROUP_FAIL = '[User] REMOVE_USER_FROM_GROUP_FAIL',
1420
DEFAULT_USER = '[USER] DEFAULT_USER',
1521
}
1622

@@ -57,6 +63,37 @@ export class RevokeRoleUserFail implements Action {
5763
public readonly type = UserActionTypes.REVOKE_USER_ROLE_FAIL;
5864
constructor(public error: string) {}
5965
}
66+
67+
export class AddUserToGroup implements Action {
68+
public readonly type = UserActionTypes.ADD_USER_TO_GROUP;
69+
constructor(public userId: string, public groupName: string) {}
70+
}
71+
72+
export class AddUserToGroupSuccess implements Action {
73+
public readonly type = UserActionTypes.ADD_USER_TO_GROUP_SUCCESS;
74+
constructor(readonly payload: User) {}
75+
}
76+
77+
export class AddUserToGroupFail implements Action {
78+
public readonly type = UserActionTypes.ADD_USER_TO_GROUP_FAIL;
79+
constructor(public error: string) {}
80+
}
81+
82+
export class RemoveUserFromGroup implements Action {
83+
public readonly type = UserActionTypes.REMOVE_USER_FROM_GROUP;
84+
constructor(public userId: string, public groupName: string) {}
85+
}
86+
87+
export class RemoveUserFromGroupSuccess implements Action {
88+
public readonly type = UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS;
89+
constructor(readonly payload: User) {}
90+
}
91+
92+
export class RemoveUserFromGroupFail implements Action {
93+
public readonly type = UserActionTypes.REMOVE_USER_FROM_GROUP_FAIL;
94+
constructor(public error: string) {}
95+
}
96+
6097
export class DefaultUser implements Action {
6198
public readonly type = UserActionTypes.DEFAULT_USER;
6299
}
@@ -71,4 +108,10 @@ export type UserActions =
71108
| GrantRoleUserFail
72109
| RevokeRoleUser
73110
| RevokeRoleUserSuccess
74-
| RevokeRoleUserFail;
111+
| RevokeRoleUserFail
112+
| AddUserToGroup
113+
| AddUserToGroupSuccess
114+
| AddUserToGroupFail
115+
| RemoveUserFromGroup
116+
| RemoveUserFromGroupSuccess
117+
| RemoveUserFromGroupFail;

src/app/modules/users/store/user.effects.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,76 @@ describe('UserEffects', () => {
105105
expect(action.type).toEqual(UserActionTypes.REVOKE_USER_ROLE_FAIL);
106106
});
107107
});
108+
109+
it('action type is ADD_USER_TO_GROUP_SUCCESS when service is executed sucessfully', async () => {
110+
const userId = 'userId';
111+
const groupName = 'groupName';
112+
actions$ = of({
113+
type: UserActionTypes.ADD_USER_TO_GROUP,
114+
userId,
115+
groupName,
116+
});
117+
118+
spyOn(toastrService, 'success');
119+
spyOn(service, 'addUserToGroup').and.returnValue(of(user));
120+
121+
effects.addUserToGroup$.subscribe((action) => {
122+
expect(toastrService.success).toHaveBeenCalledWith('Add user to group success');
123+
expect(action.type).toEqual(UserActionTypes.ADD_USER_TO_GROUP_SUCCESS);
124+
});
125+
});
126+
127+
it('action type is ADD_USER_TO_GROUP_FAIL when service is executed and fail', async () => {
128+
const userId = 'userId';
129+
const groupName = 'groupName';
130+
actions$ = of({
131+
type: UserActionTypes.ADD_USER_TO_GROUP,
132+
userId,
133+
groupName,
134+
});
135+
136+
spyOn(toastrService, 'error');
137+
spyOn(service, 'addUserToGroup').and.returnValue(throwError({ error: { message: 'error' } }));
138+
139+
effects.addUserToGroup$.subscribe((action) => {
140+
expect(toastrService.error).toHaveBeenCalled();
141+
expect(action.type).toEqual(UserActionTypes.ADD_USER_TO_GROUP_FAIL);
142+
});
143+
});
144+
145+
it('action type is REMOVE_USER_FROM_GROUP_SUCCESS when service is executed succesfully', async () => {
146+
const userId = 'userId';
147+
const groupName = 'groupName';
148+
actions$ = of({
149+
type: UserActionTypes.REMOVE_USER_FROM_GROUP,
150+
userId,
151+
groupName,
152+
});
153+
154+
spyOn(toastrService, 'success');
155+
spyOn(service, 'removeUserFromGroup').and.returnValue(of(user));
156+
157+
effects.removeUserFromGroup$.subscribe((action) => {
158+
expect(toastrService.success).toHaveBeenCalledWith('Remove user from group success');
159+
expect(action.type).toEqual(UserActionTypes.REMOVE_USER_FROM_GROUP_SUCCESS);
160+
});
161+
});
162+
163+
it('action type is REMOVE_USER_FROM_GROUP_FAIL when service is executed succesfully', async () => {
164+
const userId = 'userId';
165+
const groupName = 'groupName';
166+
actions$ = of({
167+
type: UserActionTypes.REMOVE_USER_FROM_GROUP,
168+
userId,
169+
groupName,
170+
});
171+
172+
spyOn(toastrService, 'error');
173+
spyOn(service, 'removeUserFromGroup').and.returnValue(throwError({ error: { message: 'error' } }));
174+
175+
effects.removeUserFromGroup$.subscribe((action) => {
176+
expect(toastrService.error).toHaveBeenCalled();
177+
expect(action.type).toEqual(UserActionTypes.REMOVE_USER_FROM_GROUP_FAIL);
178+
});
179+
});
108180
});

src/app/modules/users/store/user.effects.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,40 @@ export class UserEffects {
6363
)
6464
)
6565
);
66+
67+
@Effect()
68+
addUserToGroup$: Observable<Action> = this.actions$.pipe(
69+
ofType(actions.UserActionTypes.ADD_USER_TO_GROUP),
70+
map((action: actions.AddUserToGroup) => action),
71+
mergeMap((action) =>
72+
this.userService.addUserToGroup(action.userId, action.groupName).pipe(
73+
map((response) => {
74+
this.toastrService.success('Add user to group success');
75+
return new actions.AddUserToGroupSuccess(response);
76+
}),
77+
catchError((error) => {
78+
this.toastrService.error(error.error.message);
79+
return of(new actions.AddUserToGroupFail(error));
80+
})
81+
)
82+
)
83+
);
84+
85+
@Effect()
86+
removeUserFromGroup$: Observable<Action> = this.actions$.pipe(
87+
ofType(actions.UserActionTypes.REMOVE_USER_FROM_GROUP),
88+
map((action: actions.RemoveUserFromGroup) => action),
89+
mergeMap((action) =>
90+
this.userService.removeUserFromGroup(action.userId, action.groupName).pipe(
91+
map((response) => {
92+
this.toastrService.success('Remove user from group success');
93+
return new actions.RemoveUserFromGroupSuccess(response);
94+
}),
95+
catchError((error) => {
96+
this.toastrService.error(error.error.message);
97+
return of(new actions.RemoveUserFromGroupFail(error));
98+
})
99+
)
100+
)
101+
);
66102
}

src/app/modules/users/store/user.reducer.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,70 @@ describe('userReducer', () => {
9393
expect(state.isLoading).toEqual(false);
9494
});
9595

96+
it('on AddUserToGroup, isLoading is true', () => {
97+
const userId = 'userId';
98+
const groupName = 'groupName';
99+
const action = new actions.AddUserToGroup(userId, groupName);
100+
const state = userReducer(initialState, action);
101+
102+
expect(state.isLoading).toEqual(true);
103+
});
104+
105+
it('on AddUserToGroupSuccess, user groups should change', () => {
106+
const currentState: UserState = {
107+
data: [{ id: 'id', name: 'name', email: 'email', groups: null }],
108+
isLoading: false,
109+
message: '',
110+
};
111+
const userWithGroupAdded: User = { id: 'id', name: 'name', email: 'email', groups: ['group'] };
112+
const action = new actions.AddUserToGroupSuccess(userWithGroupAdded);
113+
const state = userReducer(currentState, action);
114+
115+
expect(state.data).toEqual([userWithGroupAdded]);
116+
expect(state.isLoading).toEqual(false);
117+
expect(state.message).toEqual('Add user to group success');
118+
});
119+
120+
it('on AddUserToGroupFail, should show a message with an error message', () => {
121+
const action = new actions.AddUserToGroupFail('error');
122+
const state = userReducer(initialState, action);
123+
124+
expect(state.message).toEqual('Something went wrong adding user to group');
125+
expect(state.isLoading).toEqual(false);
126+
});
127+
128+
it('on RemoveUserFromGroup, isLoading is true', () => {
129+
const userId = 'userId';
130+
const groupName = 'groupName';
131+
const action = new actions.RemoveUserFromGroup(userId, groupName);
132+
const state = userReducer(initialState, action);
133+
134+
expect(state.isLoading).toEqual(true);
135+
});
136+
137+
it('on RemoveUserFromGroupSuccess, user groups should change', () => {
138+
const currentState: UserState = {
139+
data: [{ id: 'id', name: 'name', email: 'email', groups: ['group'] }],
140+
isLoading: false,
141+
message: '',
142+
};
143+
const userWithGroupRemoved: User = { id: 'id', name: 'name', email: 'email', groups: null };
144+
const action = new actions.RemoveUserFromGroupSuccess(userWithGroupRemoved);
145+
const state = userReducer(currentState, action);
146+
147+
expect(state.data).toEqual([userWithGroupRemoved]);
148+
expect(state.isLoading).toEqual(false);
149+
expect(state.message).toEqual('Remove user from group success');
150+
});
151+
152+
it('on RemoveUserFromGroupFail, should show a message with an error message', () => {
153+
const action = new actions.RemoveUserFromGroupFail('error');
154+
const state = userReducer(initialState, action);
155+
156+
expect(state.message).toEqual('Something went wrong removing user from group');
157+
expect(state.isLoading).toEqual(false);
158+
});
159+
96160
it('on Default, ', () => {
97161
const action = new actions.DefaultUser();
98162
const state = userReducer(initialState, action);

0 commit comments

Comments
 (0)