diff --git a/src/__tests__/mockUserPoolService.ts b/src/__tests__/mockUserPoolService.ts index e1c8a4b2..56d6690e 100644 --- a/src/__tests__/mockUserPoolService.ts +++ b/src/__tests__/mockUserPoolService.ts @@ -6,7 +6,7 @@ export const newMockUserPoolService = ( Id: "test", } ): jest.Mocked => ({ - options: config, + addUserToGroup: jest.fn(), deleteAppClient: jest.fn(), deleteGroup: jest.fn(), deleteUser: jest.fn(), @@ -15,6 +15,7 @@ export const newMockUserPoolService = ( getUserByUsername: jest.fn(), listGroups: jest.fn(), listUsers: jest.fn(), + options: config, removeUserFromGroup: jest.fn(), saveAppClient: jest.fn(), saveGroup: jest.fn(), diff --git a/src/services/userPoolService.test.ts b/src/services/userPoolService.test.ts index 3db701f1..380215a4 100644 --- a/src/services/userPoolService.test.ts +++ b/src/services/userPoolService.test.ts @@ -693,4 +693,85 @@ describe("User Pool Service", () => { expect(userPool.options.MfaConfiguration).toEqual("ON"); }); }); + + describe("addUserToGroup", () => { + it("updates the group's members", async () => { + const ds = newMockDataStore(); + const userPool = new UserPoolServiceImpl( + mockClientsDataStore, + clock, + ds, + { + Id: "test", + } + ); + + const user = TDB.user(); + const group = TDB.group(); + + await userPool.addUserToGroup(TestContext, group, user); + + expect(ds.set).toHaveBeenCalledWith( + TestContext, + ["Groups", group.GroupName], + { + ...group, + LastModifiedDate: clock.get(), + members: [user.Username], + } + ); + }); + + it("only adds the user once", async () => { + const ds = newMockDataStore(); + const userPool = new UserPoolServiceImpl( + mockClientsDataStore, + clock, + ds, + { + Id: "test", + } + ); + + const user = TDB.user(); + const group = TDB.group({ + members: [user.Username], + }); + + await userPool.addUserToGroup(TestContext, group, user); + + expect(ds.set).not.toHaveBeenCalled(); + }); + }); + + describe("removeUserFromGroup", () => { + it("updates the group's members", async () => { + const ds = newMockDataStore(); + const userPool = new UserPoolServiceImpl( + mockClientsDataStore, + clock, + ds, + { + Id: "test", + } + ); + + const user = TDB.user(); + const group = TDB.group({ + members: [user.Username], + }); + + await userPool.removeUserFromGroup(TestContext, group, user); + + expect(ds.set).toHaveBeenCalledWith( + TestContext, + ["Groups", group.GroupName], + { + ...group, + LastModifiedDate: clock.get(), + members: [], + } + ); + }); + }); }); diff --git a/src/services/userPoolService.ts b/src/services/userPoolService.ts index fbe0a885..f83e73c0 100644 --- a/src/services/userPoolService.ts +++ b/src/services/userPoolService.ts @@ -138,6 +138,7 @@ export type UserPool = UserPoolType & { export interface UserPoolService { readonly options: UserPool; + addUserToGroup(ctx: Context, group: Group, user: User): Promise; saveAppClient(ctx: Context, appClient: AppClient): Promise; deleteAppClient(ctx: Context, appClient: AppClient): Promise; deleteGroup(ctx: Context, group: Group): Promise; @@ -346,6 +347,27 @@ export class UserPoolServiceImpl implements UserPoolService { return Object.values(groups); } + public async addUserToGroup( + ctx: Context, + group: Group, + user: User + ): Promise { + ctx.logger.debug( + { username: user.Username, groupName: group.GroupName }, + "UserPoolServiceImpl.addUserToFromGroup" + ); + + const groupMembers = new Set(group.members ?? []); + if (!groupMembers.has(user.Username)) { + groupMembers.add(user.Username); + await this.saveGroup(ctx, { + ...group, + LastModifiedDate: this.clock.get(), + members: Array.from(groupMembers), + }); + } + } + public async removeUserFromGroup( ctx: Context, group: Group, diff --git a/src/targets/adminAddUserToGroup.test.ts b/src/targets/adminAddUserToGroup.test.ts index 1061e8c7..c323d171 100644 --- a/src/targets/adminAddUserToGroup.test.ts +++ b/src/targets/adminAddUserToGroup.test.ts @@ -22,7 +22,6 @@ describe("AdminAddUserToGroup target", () => { clock = new ClockFake(originalDate); adminAddUserToGroup = AdminAddUserToGroup({ - clock, cognito: newMockCognitoService(mockUserPoolService), }); }); @@ -43,41 +42,11 @@ describe("AdminAddUserToGroup target", () => { UserPoolId: "test", }); - expect(mockUserPoolService.saveGroup).toHaveBeenCalledWith(TestContext, { - ...existingGroup, - LastModifiedDate: newDate, - members: [existingUser.Username], - }); - }); - - it("adds the user to a group only once", async () => { - const existingGroup = TDB.group(); - const existingUser = TDB.user(); - - mockUserPoolService.getGroupByGroupName.mockResolvedValue(existingGroup); - mockUserPoolService.getUserByUsername.mockResolvedValue(existingUser); - - const newDate = new Date(); - clock.advanceTo(newDate); - - await adminAddUserToGroup(TestContext, { - GroupName: existingGroup.GroupName, - Username: existingUser.Username, - UserPoolId: "test", - }); - - // try add a second time - await adminAddUserToGroup(TestContext, { - GroupName: existingGroup.GroupName, - Username: existingUser.Username, - UserPoolId: "test", - }); - - expect(mockUserPoolService.saveGroup).toHaveBeenCalledWith(TestContext, { - ...existingGroup, - LastModifiedDate: newDate, - members: [existingUser.Username], - }); + expect(mockUserPoolService.addUserToGroup).toHaveBeenCalledWith( + TestContext, + existingGroup, + existingUser + ); }); it("throws if the group doesn't exist", async () => { diff --git a/src/targets/adminAddUserToGroup.ts b/src/targets/adminAddUserToGroup.ts index 5d8ebbcd..19e8a19d 100644 --- a/src/targets/adminAddUserToGroup.ts +++ b/src/targets/adminAddUserToGroup.ts @@ -1,18 +1,14 @@ import { AdminAddUserToGroupRequest } from "aws-sdk/clients/cognitoidentityserviceprovider"; import { GroupNotFoundError, UserNotFoundError } from "../errors"; import { Services } from "../services"; -import { Group } from "../services/userPoolService"; import { Target } from "./Target"; export type AdminAddUserToGroupTarget = Target; -type AdminAddUserToGroupServices = Pick; +type AdminAddUserToGroupServices = Pick; export const AdminAddUserToGroup = - ({ - clock, - cognito, - }: AdminAddUserToGroupServices): AdminAddUserToGroupTarget => + ({ cognito }: AdminAddUserToGroupServices): AdminAddUserToGroupTarget => async (ctx, req) => { const userPool = await cognito.getUserPool(ctx, req.UserPoolId); @@ -26,16 +22,7 @@ export const AdminAddUserToGroup = throw new UserNotFoundError(); } - const groupUsers = new Set(group.members ?? []); - groupUsers.add(user.Username); - - const updatedGroup: Group = { - ...group, - LastModifiedDate: clock.get(), - members: Array.from(groupUsers.values()), - }; - - await userPool.saveGroup(ctx, updatedGroup); + await userPool.addUserToGroup(ctx, group, user); return {}; };