Skip to content

Commit

Permalink
IE-17/command-pattern = modify to use command pattern
Browse files Browse the repository at this point in the history
IE-17/command-pattern = refactor validation into planCommands

IE-17/command-pattern = change getCommand to singular
  • Loading branch information
vuthikxkol committed Nov 20, 2023
1 parent b37b6d5 commit 51710ef
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 47 deletions.
74 changes: 41 additions & 33 deletions src/scripts/activate-user.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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])(
Expand All @@ -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);
}
Expand All @@ -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();
});
Expand All @@ -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
Expand All @@ -124,21 +126,23 @@ 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(
'User [user_id] does not exist. Can not activate.'
);

// 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(
Expand Down Expand Up @@ -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
Expand All @@ -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');
Expand Down
107 changes: 93 additions & 14 deletions src/scripts/activate-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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<string, readonly Command[]> =>
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.
Expand Down Expand Up @@ -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<string, User> =>
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<string, User> =>
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<string, readonly User[]> => {
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<string, readonly User[]> => {
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<string, User>
): TE.TaskEither<string, User> =>
dryRun: boolean,
sendEmail: boolean
): TE.TaskEither<string, readonly User[]> =>
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 (
Expand Down Expand Up @@ -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<string, User> = 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
Expand Down

0 comments on commit 51710ef

Please sign in to comment.