Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DTFS2-7709): add manual approval needed page to amendments #4182

Merged
merged 9 commits into from
Jan 30, 2025
4 changes: 2 additions & 2 deletions e2e-tests/gef/cypress/e2e/pages/amendments/eligibility.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const eligibility = {
errorSummary: () => cy.get('[data-cy="error-summary"]'),
criterionInlineError: (id) => cy.get(`[data-cy="inline-error-criterion-${id}"]`),
allTrueRadioButtons: () => cy.get('*[data-cy^="true-radio-criterion-"]'),
allFalseRadioButtons: () => cy.get('*[data-cy^="true-radio-criterion-"]'),
allTrueRadioButtons: () => cy.get('[data-cy^="true-radio-criterion-"]'),
allFalseRadioButtons: () => cy.get('[data-cy^="false-radio-criterion-"]'),
criterionTrueRadioButton: (id) => cy.get(`[data-cy="true-radio-criterion-${id}"]`),
criterionFalseRadioButton: (id) => cy.get(`[data-cy="false-radio-criterion-${id}"]`),
criterionRadiosText: (id) => cy.get(`[data-cy="radio-wrapper-${id}"]`),
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const manualApprovalNeeded = {
pageHeading: () => cy.get('[data-cy="page-heading"]'),
backLink: () => cy.get('[data-cy="back-link"]'),
emailLink: () => cy.get('[data-cy="form-email-link"]'),
returnLink: () => cy.get('[data-cy="return-link"]'),
};

module.exports = manualApprovalNeeded;
Original file line number Diff line number Diff line change
@@ -120,4 +120,27 @@ context('Amendments - Eligibility - page tests', () => {

cy.url().should('eq', relative(`/gef/application-details/${dealId}/facilities/${facilityId}/amendments/${amendmentId}/cancel`));
});

it('should navigate to the manual approval information page if "false" is selected for all criteria', () => {
eligibility.allFalseRadioButtons().click({ multiple: true });
cy.clickContinueButton();

cy.url().should('eq', relative(`/gef/application-details/${dealId}/facilities/${facilityId}/amendments/${amendmentId}/manual-approval-needed`));
});

it('should navigate to the manual approval information page if "false" is selected for all criteria', () => {
eligibility.allTrueRadioButtons().click({ multiple: true });
eligibility.criterionFalseRadioButton(2).click();

cy.clickContinueButton();

cy.url().should('eq', relative(`/gef/application-details/${dealId}/facilities/${facilityId}/amendments/${amendmentId}/manual-approval-needed`));
});

it('should navigate to the effective from page if "true" is selected for all criteria', () => {
eligibility.allTrueRadioButtons().click({ multiple: true });
cy.clickContinueButton();

cy.url().should('eq', relative(`/gef/application-details/${dealId}/facilities/${facilityId}/amendments/${amendmentId}/effective-date`));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import relative from '../../../../relativeURL';
import MOCK_USERS from '../../../../../../../e2e-fixtures/portal-users.fixture';
import { MOCK_APPLICATION_AIN_DRAFT } from '../../../../../../../e2e-fixtures/gef/mocks/mock-deals';
import { anIssuedCashFacility } from '../../../../../../../e2e-fixtures/mock-gef-facilities';
import { applicationPreview } from '../../../../../../../gef/cypress/e2e/pages';
import whatDoYouNeedToChange from '../../../../../../../gef/cypress/e2e/pages/amendments/what-do-you-need-to-change';
import facilityValue from '../../../../../../../gef/cypress/e2e/pages/amendments/facility-value';
import eligibility from '../../../../../../../gef/cypress/e2e/pages/amendments/eligibility';
import manualApprovalNeeded from '../../../../../../../gef/cypress/e2e/pages/amendments/manual-approval-needed';

const { BANK1_MAKER1 } = MOCK_USERS;

context('Amendments - Eligibility - page tests', () => {
/**
* @type {string}
*/
let dealId;

/**
* @type {string}
*/
let facilityId;
/**
* @type {string}
*/
let amendmentId;

before(() => {
cy.insertOneGefDeal(MOCK_APPLICATION_AIN_DRAFT, BANK1_MAKER1).then((insertedDeal) => {
dealId = insertedDeal._id;

cy.updateGefDeal(dealId, MOCK_APPLICATION_AIN_DRAFT, BANK1_MAKER1);

cy.createGefFacilities(dealId, [anIssuedCashFacility({ facilityEndDateEnabled: true })], BANK1_MAKER1).then((createdFacility) => {
facilityId = createdFacility.details._id;

cy.makerLoginSubmitGefDealForReview(insertedDeal);
cy.checkerLoginSubmitGefDealToUkef(insertedDeal);

cy.clearSessionCookies();
cy.login(BANK1_MAKER1);
cy.saveSession();
cy.visit(relative(`/gef/application-details/${dealId}`));

applicationPreview.makeAChangeButton(facilityId).click();

cy.url().then((url) => {
const urlSplit = url.split('/');

amendmentId = urlSplit[9];
});

whatDoYouNeedToChange.facilityValueCheckbox().click();
cy.clickContinueButton();
cy.keyboardInput(facilityValue.facilityValue(), '10000');
cy.clickContinueButton();
eligibility.allFalseRadioButtons().click({ multiple: true });
cy.clickContinueButton();
});
});
});

after(() => {
cy.clearCookies();
cy.clearSessionCookies();
});

beforeEach(() => {
cy.clearSessionCookies();
cy.login(BANK1_MAKER1);
cy.visit(relative(`/gef/application-details/${dealId}/facilities/${facilityId}/amendments/${amendmentId}/manual-approval-needed`));
});

it('should render key features of the page', () => {
manualApprovalNeeded.pageHeading().contains('This amendment cannot be automatically approved');
manualApprovalNeeded.returnLink();
manualApprovalNeeded.emailLink();
manualApprovalNeeded.backLink();
});

it('should navigate to the deals overview page when the return link is clicked', () => {
manualApprovalNeeded.returnLink().click();

cy.url().should('eq', relative(`/dashboard/deals/0`));
});

it('should navigate to the eligibility page with pre-filled data when "back" is clicked', () => {
manualApprovalNeeded.backLink().click();

cy.url().should('eq', relative(`/gef/application-details/${dealId}/facilities/${facilityId}/amendments/${amendmentId}/eligibility`));
eligibility.allFalseRadioButtons().should('be.checked');
});
});
236 changes: 236 additions & 0 deletions gef-ui/api-tests/amendments/manual-approval-needed.get.api-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import { Headers } from 'node-mocks-http';
import { NextFunction, Request, Response } from 'express';
import { DEAL_STATUS, DEAL_SUBMISSION_TYPE, ROLES } from '@ukef/dtfs2-common';
import { HttpStatusCode } from 'axios';
import { withRoleValidationApiTests } from '../common-tests/role-validation-api-tests';
import app from '../../server/createApp';
import { createApi } from '../create-api';
import api from '../../server/services/api';
import * as storage from '../test-helpers/storage/storage';
import { PortalFacilityAmendmentWithUkefIdMockBuilder } from '../../test-helpers/mock-amendment';
import { PORTAL_AMENDMENT_PAGES } from '../../server/constants/amendments';
import { MOCK_BASIC_DEAL } from '../../server/utils/mocks/mock-applications';
import { MOCK_ISSUED_FACILITY, MOCK_UNISSUED_FACILITY } from '../../server/utils/mocks/mock-facilities';
import { getAmendmentsUrl } from '../../server/controllers/amendments/helpers/navigation.helper.ts';

const originalEnv = { ...process.env };

const { get } = createApi(app);

jest.mock('csurf', () => () => (_req: Request, _res: Response, next: NextFunction) => next());
jest.mock('../../server/middleware/csrf', () => ({
csrfToken: () => (_req: Request, _res: Response, next: NextFunction) => next(),
}));

const mockGetFacility = jest.fn();
const mockGetApplication = jest.fn();
const mockGetAmendment = jest.fn();

const dealId = '123';
const facilityId = '111';
const amendmentId = '111';

const mockDeal = { ...MOCK_BASIC_DEAL, submissionType: DEAL_SUBMISSION_TYPE.AIN, status: DEAL_STATUS.UKEF_ACKNOWLEDGED };

const url = `/application-details/${dealId}/facilities/${facilityId}/amendments/${amendmentId}/${PORTAL_AMENDMENT_PAGES.MANUAL_APPROVAL_NEEDED}`;

describe(`GET ${url}`, () => {
let sessionCookie: string;

beforeEach(async () => {
await storage.flush();
jest.resetAllMocks();

({ sessionCookie } = await storage.saveUserSession([ROLES.MAKER]));
jest.spyOn(api, 'getFacility').mockImplementation(mockGetFacility);
jest.spyOn(api, 'getApplication').mockImplementation(mockGetApplication);
jest.spyOn(api, 'getAmendment').mockImplementation(mockGetAmendment);

mockGetFacility.mockResolvedValue(MOCK_ISSUED_FACILITY);
mockGetApplication.mockResolvedValue(mockDeal);
mockGetAmendment.mockResolvedValue(
new PortalFacilityAmendmentWithUkefIdMockBuilder()
.withDealId(dealId)
.withFacilityId(facilityId)
.withAmendmentId(amendmentId)
.withCriteria([
{ id: 1, text: 'Criterion 1', answer: false },
{ id: 2, text: 'Criterion 2', answer: true },
])
.build(),
);
});

afterAll(async () => {
jest.resetAllMocks();
await storage.flush();
process.env = originalEnv;
});

describe('when FF_PORTAL_FACILITY_AMENDMENTS_ENABLED is disabled', () => {
beforeEach(() => {
process.env.FF_PORTAL_FACILITY_AMENDMENTS_ENABLED = 'false';
});

it('should redirect to /not-found', async () => {
// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Found);
expect(response.headers.location).toEqual('/not-found');
});
});

describe('when FF_PORTAL_FACILITY_AMENDMENTS_ENABLED feature flag is not set', () => {
beforeEach(() => {
delete process.env.FF_PORTAL_FACILITY_AMENDMENTS_ENABLED;
});

it('should redirect to /not-found', async () => {
// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Found);
expect(response.headers.location).toEqual('/not-found');
});
});

describe('when FF_PORTAL_FACILITY_AMENDMENTS_ENABLED is enabled', () => {
beforeEach(() => {
process.env.FF_PORTAL_FACILITY_AMENDMENTS_ENABLED = 'true';
});

withRoleValidationApiTests({
makeRequestWithHeaders: (headers: Headers) => get(url, {}, headers),
whitelistedRoles: [ROLES.MAKER],
successCode: HttpStatusCode.Ok,
});

it('should render manual approval needed page', async () => {
// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Ok);
expect(response.text).toContain('This amendment cannot be automatically approved');
});

it('should redirect to /not-found when facility not found', async () => {
// Arrange
mockGetFacility.mockResolvedValue({ details: undefined });

// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Found);
expect(response.headers.location).toEqual('/not-found');
});

it('should redirect to /not-found when deal not found', async () => {
// Arrange
mockGetApplication.mockResolvedValue(undefined);

// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Found);
expect(response.headers.location).toEqual('/not-found');
});

it('should redirect to /not-found when amendment not found', async () => {
// Arrange
mockGetAmendment.mockResolvedValue(undefined);

// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Found);
expect(response.headers.location).toEqual('/not-found');
});

it('should redirect to deal summary page when facility cannot be amended', async () => {
// Arrange
mockGetApplication.mockResolvedValue(MOCK_UNISSUED_FACILITY);

// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Found);
expect(response.headers.location).toEqual(`/gef/application-details/${dealId}`);
});

it('should redirect to the eligibility page if there are no false responses to the criteria', async () => {
// Arrange
mockGetAmendment.mockResolvedValueOnce(
new PortalFacilityAmendmentWithUkefIdMockBuilder()
.withDealId(dealId)
.withFacilityId(facilityId)
.withAmendmentId(amendmentId)
.withCriteria([
{ id: 1, text: 'Criterion 1', answer: true },
{ id: 2, text: 'Criterion 2', answer: true },
])
.build(),
);

// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Found);
expect(response.headers.location).toEqual(getAmendmentsUrl({ dealId, facilityId, amendmentId, page: PORTAL_AMENDMENT_PAGES.ELIGIBILITY }));
});

it('should render `problem with service` if getApplication throws an error', async () => {
// Arrange
mockGetApplication.mockRejectedValue(new Error('test error'));

// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Ok);
expect(response.text).toContain('Problem with the service');
});

it('should render `problem with service` if getFacility throws an error', async () => {
// Arrange
mockGetFacility.mockRejectedValue(new Error('test error'));

// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Ok);
expect(response.text).toContain('Problem with the service');
});

it('should render `problem with service` if getAmendment throws an error', async () => {
// Arrange
mockGetAmendment.mockRejectedValue(new Error('test error'));

// Act
const response = await getWithSessionCookie(sessionCookie);

// Assert
expect(response.status).toEqual(HttpStatusCode.Ok);
expect(response.text).toContain('Problem with the service');
});
});
});

function getWithSessionCookie(sessionCookie: string) {
return get(
url,
{},
{
Cookie: [`dtfs-session=${encodeURIComponent(sessionCookie)}`],
},
);
}
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.