From 51710efadb6538b41d1715b311fe66eb0643b8e1 Mon Sep 17 00:00:00 2001 From: Vuthik Kol Date: Mon, 20 Nov 2023 13:33:57 +1100 Subject: [PATCH] IE-17/command-pattern = modify to use command pattern IE-17/command-pattern = refactor validation into planCommands IE-17/command-pattern = change getCommand to singular --- src/scripts/activate-user.test.ts | 74 ++++++++++++--------- src/scripts/activate-user.ts | 107 ++++++++++++++++++++++++++---- 2 files changed, 134 insertions(+), 47 deletions(-) diff --git a/src/scripts/activate-user.test.ts b/src/scripts/activate-user.test.ts index 15e56fda..d14d5570 100644 --- a/src/scripts/activate-user.test.ts +++ b/src/scripts/activate-user.test.ts @@ -11,11 +11,7 @@ import { baseUserService, deactivatedUser, } from './__fixtures__/data-providers'; -import { - activateUser, - activateUserHandler, - dryRunActivateUser, -} from './activate-user'; +import { activateUserInvoker } from './activate-user'; describe('Activating users', () => { it.each([okta.UserStatus.DEPROVISIONED, okta.UserStatus.STAGED])( @@ -33,24 +29,26 @@ describe('Activating users', () => { }; // When we attempt to activate a user with status that isn't DEPROVISIONED or STAGED - const activateUserResult = await activateUserHandler( + const activateUserResult = await activateUserInvoker( userService, user.id, - activateUser(userService, false) + false, + false )(); // Then we should have a left - expect(activateUserResult).toEqualRight(user); + expect(activateUserResult).toEqualRight([user]); // When we attempt to activate a user with status that isn't DEPROVISIONED or STAGED and send activation email - const activateUserAndSendEmailResult = await activateUserHandler( + const activateUserAndSendEmailResult = await activateUserInvoker( userService, user.id, - activateUser(userService, true) + false, + false )(); // Then we should have a left - expect(activateUserAndSendEmailResult).toEqualRight(user); + expect(activateUserAndSendEmailResult).toEqualRight([user]); expect(userService.activateUser).toHaveBeenCalledTimes(2); } @@ -65,22 +63,24 @@ describe('Activating users', () => { }; // When we attempt to activate the user in dry run mode - const activateUserResult = await activateUserHandler( + const activateUserResult = await activateUserInvoker( userService, deactivatedUser.id, - dryRunActivateUser(false) + true, + false )(); // Then the activateUser function should not have been called - expect(activateUserResult).toEqualRight(deactivatedUser); + expect(activateUserResult).toEqualRight([deactivatedUser]); // When we attempt to activate the user and send activation email in dry run mode - const activateUserAndSendEmailResult = await activateUserHandler( + const activateUserAndSendEmailResult = await activateUserInvoker( userService, deactivatedUser.id, - dryRunActivateUser(true) + true, + true )(); // Then the activateUser function should not have been called - expect(activateUserAndSendEmailResult).toEqualRight(deactivatedUser); + expect(activateUserAndSendEmailResult).toEqualRight([deactivatedUser]); expect(userService.activateUser).not.toHaveBeenCalled(); }); @@ -94,20 +94,22 @@ describe('Activating users', () => { }; // When we attempt to activate the user and the request fails - const activateUserResult = await activateUserHandler( + const activateUserResult = await activateUserInvoker( userService, deactivatedUser.id, - activateUser(userService, false) + false, + false )(); // Then we should have a left expect(activateUserResult).toEqualLeft('expected error'); // When we attempt to activate the user and send activation email, but the request fails - const activateUserAndSendEmailResult = await activateUserHandler( + const activateUserAndSendEmailResult = await activateUserInvoker( userService, deactivatedUser.id, - activateUser(userService, true) + false, + true )(); // Then we should have a left @@ -124,10 +126,11 @@ describe('Activating users', () => { }; // When we attempt to activate a non-existent user - const activateUserResult = await activateUserHandler( + const activateUserResult = await activateUserInvoker( userService, deactivatedUser.id, - activateUser(userService, false) + false, + false )(); // Then we should have a left expect(activateUserResult).toEqualLeft( @@ -135,10 +138,11 @@ describe('Activating users', () => { ); // When we attempt to activate a non-existent user and send activation email - const activateUserSendEmailResult = await activateUserHandler( + const activateUserSendEmailResult = await activateUserInvoker( userService, deactivatedUser.id, - activateUser(userService, true) + false, + true )(); // Then we should have a left expect(activateUserSendEmailResult).toEqualLeft( @@ -188,20 +192,22 @@ describe('Activating users', () => { }; // When we attempt to activate a user with status that isn't DEPROVISIONED or STAGED - const activateUserResult = await activateUserHandler( + const activateUserResult = await activateUserInvoker( userService, user.id, - activateUser(userService, false) + false, + false )(); // Then we should have a left expect(activateUserResult).toEqualLeft(errorMessage); // When we attempt to activate a user with status that isn't DEPROVISIONED or STAGED and send activation email - const activateUserSendEmailResult = await activateUserHandler( + const activateUserSendEmailResult = await activateUserInvoker( userService, user.id, - activateUser(userService, true) + false, + true )(); // Then we should have a left @@ -219,19 +225,21 @@ describe('Activating users', () => { }; // When we attempt to activate a user but retrieving the user fails - const activateUserResult = await activateUserHandler( + const activateUserResult = await activateUserInvoker( userService, deactivatedUser.id, - activateUser(userService, false) + false, + false )(); // Then we should have a left expect(activateUserResult).toEqualLeft('expected error'); // When we attempt to activate a user and send activation email but retrieving the user fails - const activateUserAndSendEmailResult = await activateUserHandler( + const activateUserAndSendEmailResult = await activateUserInvoker( userService, deactivatedUser.id, - activateUser(userService, true) + false, + true )(); // Then we should have a left expect(activateUserAndSendEmailResult).toEqualLeft('expected error'); diff --git a/src/scripts/activate-user.ts b/src/scripts/activate-user.ts index 12f4567c..5dc749eb 100644 --- a/src/scripts/activate-user.ts +++ b/src/scripts/activate-user.ts @@ -15,6 +15,20 @@ type ActivatableUser = User & { readonly status: okta.UserStatus.DEPROVISIONED | okta.UserStatus.STAGED; }; +/** + * Command to activate a user. + */ +type ActivateUserCommand = { + readonly _tag: 'ActivateUserCommand'; + readonly sendEmail: boolean; + readonly service: UserService; + readonly user: ActivatableUser; +}; +/** + * Command to execute. + */ +type Command = ActivateUserCommand; + /** * Checks to see if user exists. * @param service - the service to use to get the user. @@ -35,6 +49,7 @@ const validateUserExist = ( TE.fromOption(() => `User [${userId}] does not exist. Can not activate.`) ) ); + /** * Checks to see if user has a status of staged or deprovisioned. * @param user - the user to check the status of. @@ -88,6 +103,33 @@ const validateUserStatusPriorToActivation = ( } }; +/** + * Plans the commands to execute to activate the user. + * @param service - the service to use to activate the user. + * @param userId - the id of the user to activate. + * @param sendEmail - if true, will send activation email to the user. + * @returns a TaskEither that resolves to an array of commands to execute to activate the user. + */ +const planCommands = ( + service: UserService, + userId: string, + sendEmail: boolean +): TE.TaskEither => + pipe( + validateUserExist(service, userId), + TE.chain((user) => validateUserStatusPriorToActivation(user)), + TE.chain((user) => + TE.right([ + { + _tag: 'ActivateUserCommand', + sendEmail: sendEmail, + service: service, + user: user, + }, + ]) + ) + ); + /** * Prints out what would happen if we were to activate the user. * @param sendEmail - if true, will print out that an activation email will be sent to the user. @@ -142,22 +184,63 @@ export const activateUser = ) ); +/** + * Gets the command to execute. + * @param command - the command to get. + * @returns a TaskEither that resolves to the user. + */ +const getExecutableCommand = (command: Command): TE.TaskEither => + activateUser(command.service, command.sendEmail)(command.user); + +/** + * Gets the command to dry run. + * @param command - the command to get. + * @returns a TaskEither that resolves to the user. + */ +const getDryRunCommand = (command: Command): TE.TaskEither => + dryRunActivateUser(command.sendEmail)(command.user); + +/** + * Executes the commands. + * @param commands - the commands to execute. + * @returns a TaskEither that resolves to the users. + */ +const executeCommands = ( + commands: readonly Command[] +): TE.TaskEither => { + return TE.traverseSeqArray(getExecutableCommand)(commands); +}; + +/** + * Reports what would happen if we were to execute the commands. + * @param commands - the commands to dry run. + * @returns a TaskEither that resolves to the users. + */ +const reportDryRun = ( + commands: readonly Command[] +): TE.TaskEither => { + return TE.traverseSeqArray(getDryRunCommand)(commands); +}; + /** * Activates a user, only works if user currently has the status: staged or deprovisioned. * @param service - the service to use to activate the user. * @param userId - the id of the user to activate. - * @param dryRun - if true, will not actually activate the user, but will print out what would happen. + * @param dryRun - if true, will not activate the user, but will print out what would happen. + * @param sendEmail - if true, will send activation email to the user. * @returns a TaskEither that resolves to the activated user. */ -export const activateUserHandler = ( +export const activateUserInvoker = ( service: UserService, userId: string, - commandHandler: (user: ActivatableUser) => TE.TaskEither -): TE.TaskEither => + dryRun: boolean, + sendEmail: boolean +): TE.TaskEither => pipe( - validateUserExist(service, userId), - TE.chain((user) => validateUserStatusPriorToActivation(user)), - TE.chain((user) => commandHandler(user)) + planCommands(service, userId, sendEmail), + TE.chain((commands) => + dryRun ? reportDryRun(commands) : executeCommands(commands) + ) ); export default ( @@ -210,16 +293,12 @@ export default ( const client = oktaManageClient({ ...args }); const service = new OktaUserService(client); const { userId, dryRun, sendEmail } = args; - const commandHandler: ( - user: ActivatableUser - ) => TE.TaskEither = dryRun - ? dryRunActivateUser(sendEmail) - : activateUser(service, sendEmail); - const result = await activateUserHandler( + const result = await activateUserInvoker( service, userId, - commandHandler + dryRun, + sendEmail )(); // eslint-disable-next-line functional/no-conditional-statement