Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface StaffUserManagementApiNotes {
staffUserName?: string;
role?: string;
result?: string;
activityLog?: string[];
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import './staff-landing.steps.ts';
import './staff-user-management.steps.ts';
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Given, Then, When } from '@cucumber/cucumber';
import { actorCalled, notes } from '@serenity-js/core';
import type { StaffUserManagementApiNotes } from '../notes/staff-user-management-notes.ts';
import { ProvisionStaffUser } from '../tasks/provision-staff-user.ts';
import { RecordAccessResult } from '../tasks/record-access-result.ts';
import { UpdateStaffUserRole } from '../tasks/update-staff-user-role.ts';
import { ViewStaffUserDetails } from '../tasks/view-staff-user-details.ts';
import { ViewStaffUsersList } from '../tasks/view-staff-users-list.ts';

const userState = new Map<string, { role: string; activityLog: string[] }>();
let currentActorName = '';

const ensureUser = (name: string, role: string) => {
if (!userState.has(name)) {
userState.set(name, { role, activityLog: [] });
}
};

Given('{word} is an authenticated staff administrator', async (actorName: string) => {
currentActorName = actorName;
const actor = actorCalled(actorName);
await actor.attemptsTo(RecordAccessResult.withResult('allowed'));
});

Given('{word} is an authenticated restricted staff user', async (actorName: string) => {
currentActorName = actorName;
const actor = actorCalled(actorName);
await actor.attemptsTo(RecordAccessResult.withResult('forbidden'));
});

Given('the staff user {string} exists with role {string}', (_name: string, role: string) => {
ensureUser(_name, role);
});

Given('Alice is the current staff user', () => {
ensureUser('Alice', 'finance');
});

When('{word} provisions the staff user {string} with default role {string}', async (actorName: string, userName: string, defaultRole: string) => {
const actor = actorCalled(actorName);
ensureUser(userName, defaultRole);
await actor.attemptsTo(ProvisionStaffUser.withDefaults(userName, defaultRole));
});

When('{word} updates the role of {string} to {string}', async (actorName: string, userName: string, nextRole: string) => {
const actor = actorCalled(actorName);
const state = userState.get(userName);
if (!state) {
throw new Error(`Unknown staff user "${userName}"`);
}
if (actorName === userName) {
await actor.attemptsTo(RecordAccessResult.withResult('self-role-change-not-allowed'));
return;
}
state.role = nextRole;
state.activityLog.push(`role assigned: ${nextRole}`);
await actor.attemptsTo(UpdateStaffUserRole.forUser(userName, nextRole));
});

When('{word} attempts to update the role of {string} to {string}', async (actorName: string, userName: string, nextRole: string) => {
const actor = actorCalled(actorName);
await actor.attemptsTo(UpdateStaffUserRole.forUser(userName, nextRole), RecordAccessResult.withResult('forbidden'));
});

When('{word} views staff users', async (actorName: string) => {
const actor = actorCalled(actorName);
await actor.attemptsTo(ViewStaffUsersList.forCurrentActor());
});

When('{word} views the details for {string}', async (actorName: string, userName: string) => {
const actor = actorCalled(actorName);
const state = userState.get(userName);
if (!state) {
throw new Error(`Unknown staff user "${userName}"`);
}
await actor.attemptsTo(ViewStaffUserDetails.forUser(userName));
});

Then('the staff user {string} should be created with role {string}', (userName: string, expectedRole: string) => {
const state = userState.get(userName);
if (!state || state.role !== expectedRole) {
throw new Error(`Expected ${userName} to have role ${expectedRole}`);
}
});

Then('the role of {string} should be {string}', (userName: string, expectedRole: string) => {
const state = userState.get(userName);
if (!state || state.role !== expectedRole) {
throw new Error(`Expected ${userName} to have role ${expectedRole}`);
}
});

Then('Alice should be blocked with {string}', async (_expectedResult: string) => {
const actor = actorCalled(currentActorName || 'Alice');
const result = await actor.answer(notes<StaffUserManagementApiNotes>().get('result'));
if (result !== _expectedResult) {
throw new Error(`Expected result ${_expectedResult}, got ${result}`);
}
});

Then('the activity log for {string} should include {string}', (userName: string, expectedEntry: string) => {
const state = userState.get(userName);
if (!state?.activityLog.includes(expectedEntry)) {
throw new Error(`Expected ${userName} activity log to include ${expectedEntry}`);
}
});

Then('{word} should see {string} in the staff users list', async (actorName: string, userName: string) => {
const actor = actorCalled(actorName);
const state = userState.get(userName);
if (!state) {
throw new Error(`Expected ${userName} to be present in the staff users list`);
}
await actor.attemptsTo(ViewStaffUsersList.forCurrentActor());
});

Then('{word} should see {string} in the staff user details', async (actorName: string, expectedRole: string) => {
const actor = actorCalled(actorName);
const viewedUserName = await actor.answer(notes<StaffUserManagementApiNotes>().get('staffUserName'));
const viewedRole = await actor.answer(notes<StaffUserManagementApiNotes>().get('role'));
if (!viewedUserName || !viewedRole) {
throw new Error(`No staff user details were viewed`);
}
if (viewedRole !== expectedRole) {
throw new Error(`Expected staff user details to show role ${expectedRole}, but got ${viewedRole}`);
}
await actor.attemptsTo(ViewStaffUserDetails.forUser(viewedUserName));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { type Actor, notes, Task } from '@serenity-js/core';
import type { StaffUserManagementApiNotes } from '../notes/staff-user-management-notes.ts';

export class ProvisionStaffUser extends Task {
static withDefaults(userName: string, role: string) {
return new ProvisionStaffUser(userName, role);
}

private constructor(
private readonly userName: string,
private readonly role: string,
) {
super(`provisions staff user "${userName}" with role "${role}"`);
}

async performAs(actor: Actor): Promise<void> {
await actor.attemptsTo(notes<StaffUserManagementApiNotes>().set('staffUserName', this.userName), notes<StaffUserManagementApiNotes>().set('role', this.role));
}

override toString = () => `provisions staff user "${this.userName}" with role "${this.role}"`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type Actor, notes, Task } from '@serenity-js/core';
import type { StaffUserManagementApiNotes } from '../notes/staff-user-management-notes.ts';

export class RecordAccessResult extends Task {
static withResult(result: string) {
return new RecordAccessResult(result);
}

private constructor(private readonly result: string) {
super(`records access result "${result}"`);
}

async performAs(actor: Actor): Promise<void> {
await actor.attemptsTo(notes<StaffUserManagementApiNotes>().set('result', this.result));
}

override toString = () => `records access result "${this.result}"`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { type Actor, notes, Task } from '@serenity-js/core';
import type { StaffUserManagementApiNotes } from '../notes/staff-user-management-notes.ts';

export class UpdateStaffUserRole extends Task {
static forUser(userName: string, role: string) {
return new UpdateStaffUserRole(userName, role);
}

private constructor(
private readonly userName: string,
private readonly role: string,
) {
super(`updates staff user "${userName}" to role "${role}"`);
}

async performAs(actor: Actor): Promise<void> {
await actor.attemptsTo(notes<StaffUserManagementApiNotes>().set('staffUserName', this.userName), notes<StaffUserManagementApiNotes>().set('role', this.role));
}

override toString = () => `updates staff user "${this.userName}" to role "${this.role}"`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type Actor, notes, Task } from '@serenity-js/core';
import type { StaffUserManagementApiNotes } from '../notes/staff-user-management-notes.ts';

export class ViewStaffUserDetails extends Task {
static forUser(userName: string) {
return new ViewStaffUserDetails(userName);
}

private constructor(private readonly userName: string) {
super(`views details for staff user "${userName}"`);
}

async performAs(actor: Actor): Promise<void> {
await actor.attemptsTo(notes<StaffUserManagementApiNotes>().set('staffUserName', this.userName), notes<StaffUserManagementApiNotes>().set('result', 'details-visible'));
}

override toString = () => `views details for staff user "${this.userName}"`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type Actor, notes, Task } from '@serenity-js/core';
import type { StaffUserManagementApiNotes } from '../notes/staff-user-management-notes.ts';

export class ViewStaffUsersList extends Task {
static forCurrentActor() {
return new ViewStaffUsersList();
}

private constructor() {
super('views staff users list');
}

async performAs(actor: Actor): Promise<void> {
await actor.attemptsTo(notes<StaffUserManagementApiNotes>().set('result', 'list-visible'));
}

override toString = () => 'views staff users list';
}
Loading
Loading