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

#2322 - IER12 E2E Tests Additional Scenarios #2474

Merged
Merged
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
Expand Up @@ -8,6 +8,7 @@ import {
} from "../../../../../../../test/helpers";
import {
E2EDataSources,
createFakeDisbursementFeedbackError,
createE2EDataSources,
getUploadedFile,
saveFakeStudentRestriction,
Expand All @@ -31,6 +32,7 @@ import {
AWARDS_ONE_OF_TWO_DISBURSEMENT,
AWARDS_SINGLE_DISBURSEMENT,
AWARDS_SINGLE_DISBURSEMENT_NO_BC_FUNDING,
AWARDS_SINGLE_DISBURSEMENT_RESTRICTION_WITHHELD_FUNDS,
AWARDS_TWO_OF_TWO_DISBURSEMENT,
JANE_MONONYMOUS_FROM_OTHER_COUNTRY,
JOHN_DOE_FROM_CANADA,
Expand All @@ -44,6 +46,7 @@ import {
import { numberToText, getSuccessSummaryMessages } from "./utils/string-utils";
import { createIER12SchedulerJobMock } from "./utils";
import { isValidFileTimestamp } from "@sims/test-utils/utils";
import { FULL_TIME_DISBURSEMENT_FEEDBACK_ERRORS } from "@sims/integrations/services/disbursement-schedule/disbursement-schedule.models";

describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {
let app: INestApplication;
Expand Down Expand Up @@ -147,7 +150,6 @@ describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {
// Act
const ier12Results = await processor.processIER12File(job);
// Assert
const uploadedFile = getUploadedFile(sftpClientMock);
// Assert process result.
expect(ier12Results).toBeDefined();
// File timestamp.
Expand All @@ -157,6 +159,7 @@ describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {
getSuccessSummaryMessages(timestampResult.value, { expectedRecords: 2 }),
]);
// Assert file output.
const uploadedFile = getUploadedFile(sftpClientMock);
expect(uploadedFile.fileLines?.length).toBe(2);
const [line1, line2] = uploadedFile.fileLines;
const [firstDisbursement, secondDisbursement] =
Expand Down Expand Up @@ -217,10 +220,8 @@ describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {

// Act
const ier12Results = await processor.processIER12File(job);
db.application.createQueryBuilder();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


// Assert
const uploadedFile = getUploadedFile(sftpClientMock);
// Assert process result.
expect(ier12Results).toBeDefined();
// File timestamp.
Expand All @@ -230,6 +231,7 @@ describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {
getSuccessSummaryMessages(timestampResult.value),
]);
// Assert file output.
const uploadedFile = getUploadedFile(sftpClientMock);
expect(uploadedFile.fileLines?.length).toBe(1);
const [line1] = uploadedFile.fileLines;
const [firstDisbursement] =
Expand Down Expand Up @@ -285,10 +287,8 @@ describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {

// Act
const ier12Results = await processor.processIER12File(job);
db.application.createQueryBuilder();

// Assert
const uploadedFile = getUploadedFile(sftpClientMock);
// Assert process result.
expect(ier12Results).toBeDefined();
// File timestamp.
Expand All @@ -298,6 +298,7 @@ describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {
getSuccessSummaryMessages(timestampResult.value),
]);
// Assert file output.
const uploadedFile = getUploadedFile(sftpClientMock);
expect(uploadedFile.fileLines?.length).toBe(1);
const [line1] = uploadedFile.fileLines;
const [firstDisbursement] =
Expand Down Expand Up @@ -366,10 +367,8 @@ describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {

// Act
const ier12Results = await processor.processIER12File(job);
db.application.createQueryBuilder();

// Assert
const uploadedFile = getUploadedFile(sftpClientMock);
// Assert process result.
expect(ier12Results).toBeDefined();
// File timestamp.
Expand All @@ -379,6 +378,7 @@ describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {
getSuccessSummaryMessages(timestampResult.value),
]);
// Assert file output.
const uploadedFile = getUploadedFile(sftpClientMock);
expect(uploadedFile.fileLines?.length).toBe(1);
const [line1] = uploadedFile.fileLines;
const [firstDisbursement] =
Expand All @@ -390,4 +390,147 @@ describe(describeProcessorRootTest(QueueNames.IER12Integration), () => {
`${assessmentId}${firstDisbursementId}${defaultApplicationNumber}1 242963189Doe John 19980113B SI Address Line 1 Address Line 2 Victoria BC Z1Z1Z1 Some Program With DescripSome program with description too long graduateDiploma 0001 5 0512123401234ADR2XYZ 62000081620001010000033330000004444000000555500000066660019100F2000060120002001COMP20000601000010000000006000000004800000N NNY 20000602 000321200000000160000003212000000000000000000000001NNN000000000000000000000000000000000000000000000000000000000000NN0000000000000144430000000000000000000000000000000000007777000000070045000000000000000000000000000000000000000000000000000000002200000065004000046000000005500000 0000000000000000000000000000000000000000000000000000000000000000000000DISR20000811 Completed Pending 20000816 CSLF0000100000BCSL0000600000CSGP0000200000CSGD0000300000CSGF0000400000CSGT0000500000BCAG0000700000SBSD0000900000BGPD0000800000 0000000000`,
);
});

it("Should generate an IER12 file with one record for a student with one sent disbursement that had a feedback error reported (DISE).", async () => {
// Arrange
const testInputData = {
student: JANE_MONONYMOUS_FROM_OTHER_COUNTRY,
application: {
applicationNumber: defaultApplicationNumber,
studentNumber: undefined,
relationshipStatus: RelationshipStatus.Single,
applicationStatus: ApplicationStatus.Completed,
applicationStatusUpdatedOn: undefined,
},
assessment: {
triggerType: AssessmentTriggerType.OriginalAssessment,
assessmentDate: undefined,
workflowData: WORKFLOW_DATA_SINGLE_INDEPENDENT_WITH_NO_DEPENDENTS,
assessmentData: ASSESSMENT_DATA_SINGLE_DEPENDENT,
disbursementSchedules: [
{
coeStatus: COEStatus.completed,
disbursementScheduleStatus: DisbursementScheduleStatus.Sent,
disbursementDate: undefined,
updatedAt: undefined,
dateSent: undefined,
disbursementValues: AWARDS_SINGLE_DISBURSEMENT_NO_BC_FUNDING,
},
],
},
educationProgram: PROGRAM_GRADUATE_DIPLOMA_WITH_INSTITUTION_PROGRAM_CODE,
offering: OFFERING_FULL_TIME,
};
const application = await saveIER12TestInputData(db, testInputData, {
programYearPrefix: sharedProgramYearPrefix,
submittedDate: referenceSubmissionDate,
});
// Create a feedback error associated with the disbursement.
const [errorCode] = FULL_TIME_DISBURSEMENT_FEEDBACK_ERRORS;
const [disbursementSchedule] =
application.currentAssessment.disbursementSchedules;
const feedbackError = createFakeDisbursementFeedbackError(
{ disbursementSchedule },
{ initialValues: { errorCode, updatedAt: dateUtils.addDays(-1) } },
);
await db.disbursementFeedbackErrors.save(feedbackError);

// Queued job.
// No date provided as it is expected that the disbursement feedback error
// date would ensure the IER12 record is generated.
const job = createIER12SchedulerJobMock();
Copy link
Collaborator

@dheepak-aot dheepak-aot Nov 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IER here is running based on current date -1 and picking the IER record based on feedback error table.
Amazing to see this level of validation in E2E ❤️


// Act
const ier12Results = await processor.processIER12File(job);

// Assert
// Assert process result.
expect(ier12Results).toBeDefined();
// File timestamp.
const [timestampResult] = getFileNameAsCurrentTimestampMock.mock.results;
expect(isValidFileTimestamp(timestampResult.value)).toBe(true);
expect(ier12Results).toStrictEqual([
getSuccessSummaryMessages(timestampResult.value),
]);
// Assert file output.
const uploadedFile = getUploadedFile(sftpClientMock);
expect(uploadedFile.fileLines?.length).toBe(1);
const [line1] = uploadedFile.fileLines;
const [firstDisbursement] =
application.currentAssessment.disbursementSchedules;
const assessmentId = numberToText(application.currentAssessment.id);
// Line 1 validations.
const firstDisbursementId = numberToText(firstDisbursement.id);
expect(line1).toBe(
`${assessmentId}${firstDisbursementId}${defaultApplicationNumber} 242963189Jane With Really Long Mon 19980113B SI Some Foreign Street Addre New York SOME POSTAL CODESome Program With DescripSome program with description too long graduateDiploma 0001 5 0512123401234ADR2XYZ 62000081620001010000033330000004444000000555500000066660019100F2000060120002001COMP20000601000000000000000000000001209900N NNN 20000602 000321200000000160000003212000000000000000000000001NNN000000000000000000000000000000000000000000000000000000000000NN0000000000000144430000000000000000000000000000000000007777000000070045000000000000000000000000000000000000000000000000000000002200000065004000046000000001209900 0000000000000000000000000000000000000000000000000000000000000000000000DISE2023110220000815Completed Sent 20000816 CSLF0000000000BCSL0000000000CSGP0000759900CSGD0000065000CSGF0000150000CSGT0000235000BCAG0000000000SBSD0000000000BGPD0000000000 0000000000`,
);
});

it("Should generate an IER12 file with one record for a student with one sent disbursement that had some funds withheld due to a restriction (DISW).", async () => {
// Arrange
const testInputData = {
student: JOHN_DOE_FROM_CANADA,
application: {
applicationNumber: defaultApplicationNumber,
studentNumber: undefined,
relationshipStatus: RelationshipStatus.Married,
applicationStatus: ApplicationStatus.Completed,
applicationStatusUpdatedOn: undefined,
},
assessment: {
triggerType: AssessmentTriggerType.OriginalAssessment,
assessmentDate: undefined,
workflowData: WORKFLOW_DATA_MARRIED_WITH_DEPENDENTS,
assessmentData: ASSESSMENT_DATA_MARRIED,
disbursementSchedules: [
{
coeStatus: COEStatus.completed,
disbursementScheduleStatus: DisbursementScheduleStatus.Sent,
disbursementDate: undefined,
updatedAt: undefined,
dateSent: undefined,
disbursementValues:
AWARDS_SINGLE_DISBURSEMENT_RESTRICTION_WITHHELD_FUNDS,
},
],
},
educationProgram:
PROGRAM_UNDERGRADUATE_CERTIFICATE_WITHOUT_INSTITUTION_PROGRAM_CODE,
offering: OFFERING_FULL_TIME,
};
const application = await saveIER12TestInputData(db, testInputData, {
programYearPrefix: sharedProgramYearPrefix,
submittedDate: referenceSubmissionDate,
});

// Queued job.
const job = createIER12SchedulerJobMock(
application.currentAssessment.assessmentDate,
);

// Act
const ier12Results = await processor.processIER12File(job);

// Assert
// Assert process result.
expect(ier12Results).toBeDefined();
// File timestamp.
const [timestampResult] = getFileNameAsCurrentTimestampMock.mock.results;
expect(isValidFileTimestamp(timestampResult.value)).toBe(true);
expect(ier12Results).toStrictEqual([
getSuccessSummaryMessages(timestampResult.value),
]);
// Assert file output.
const uploadedFile = getUploadedFile(sftpClientMock);
expect(uploadedFile.fileLines?.length).toBe(1);
const [line1] = uploadedFile.fileLines;
const [firstDisbursement] =
application.currentAssessment.disbursementSchedules;
const assessmentId = numberToText(application.currentAssessment.id);
// Line 1 validations.
const firstDisbursementId = numberToText(firstDisbursement.id);
expect(line1).toBe(
`${assessmentId}${firstDisbursementId}${defaultApplicationNumber} 242963189Doe John 19980113B MA Address Line 1 Address Line 2 Victoria BC Z1Z1Z1 Program Program description undergraduateCertificate 0001 8 0512123401234ADR2 62000081620001010000033330000004444000000555500000066660019100F2000060120002001COMP20000601000010000000006598000000032400N NNN 20000602 000966867900000000000009668679003002001003000003005NNY000000000000000000000000000000000000000000000000000000000000NY0000000000000144430000000000000000000000000000000000007777000000150056000000000000000000000000000000000000000000003000000000050000000065430000009876120000792200 0000000000000000000000000000000000000000000000000000000000000000000000DISW2000081520000815Completed Sent 20000816 CSLF0000100000BCSL0000659800CSGP0000000000CSGD0000000000CSGF0000000000CSGT0000000000BCAG0000032400SBSD0000000000BGPD0000000000 0000000000`,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,28 @@ export const AWARDS_TWO_OF_TWO_DISBURSEMENT: IERAward[] = [
valueAmount: 99,
},
];

/**
* Represents the scenario where a restriction is applied and the BC funding must be held.
* All the BC related funds will not be disbursed.
*/
export const AWARDS_SINGLE_DISBURSEMENT_RESTRICTION_WITHHELD_FUNDS: IERAward[] =
[
{
valueType: DisbursementValueType.CanadaLoan,
valueCode: "CSLF",
valueAmount: 1000,
},
{
valueType: DisbursementValueType.BCLoan,
valueCode: "BCSL",
valueAmount: 6598,
restrictionAmountSubtracted: 6598,
},
{
valueType: DisbursementValueType.BCGrant,
valueCode: "BCAG",
valueAmount: 324,
restrictionAmountSubtracted: 324,
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export type IERAddressInfo = Omit<AddressInfo, "country" | "selectedCountry">;

export type IERAward = Pick<
DisbursementValue,
"valueType" | "valueCode" | "valueAmount"
"valueType" | "valueCode" | "valueAmount" | "restrictionAmountSubtracted"
>;

export const DATE_FORMAT = "YYYYMMDD";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
DisbursementFeedbackErrors,
DisbursementSchedule,
} from "@sims/sims-db";

/**
* Creates a new disbursement feedback error.
* @param relations dependencies.
* - `disbursementSchedule` associated disbursement schedule.
* @param options additional options.
* - `initialValues` initial feedback error record values.
* @returns disbursement feedback error to be saved.
*/
export function createFakeDisbursementFeedbackError(
relations: {
disbursementSchedule: DisbursementSchedule;
},
options?: {
initialValues?: Partial<DisbursementFeedbackErrors>;
},
): DisbursementFeedbackErrors {
const now = new Date();
const feedbackError = new DisbursementFeedbackErrors();
feedbackError.dateReceived = options?.initialValues?.dateReceived ?? now;
feedbackError.errorCode = options?.initialValues?.errorCode ?? "EDU-99999";
feedbackError.disbursementSchedule = relations.disbursementSchedule;
feedbackError.updatedAt = options?.initialValues?.updatedAt ?? now;
return feedbackError;
}
1 change: 1 addition & 0 deletions sources/packages/backend/libs/test-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ export * from "./factories/disbursement-receipt";
export * from "./factories/disbursement-receipt-value";
export * from "./factories/application-offering-change-request";
export * from "./factories/designation-agreement-locations";
export * from "./factories/disbursement-feedback-error";
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,20 @@ export function createSSHServiceMock(
}

/**
* Get the parameters provided to the put method of the SSH client that
* represents the data that would be uploaded to the SFTP in a real scenario.
* Get the parameters provided to the put method of the SSH client that represents the
* remote file path and the data that would be uploaded to the SFTP in a real scenario.
* @param sshClientMock SSH mocked client.
* @returns file name and file content of the supposed-to-be uploaded file.
*/
export function getUploadedFile(
sshClientMock: DeepMocked<Client>,
): UploadedFile {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a part of this PR but maybe we can update the comment to: "Get the parameters provided to the put method of the SSH client that represents the remote file path and the data that would be uploaded to the SFTP in a real scenario."

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed.

const uploadedFile = {} as UploadedFile;
if (!sshClientMock.put.mock.calls.length) {
throw new Error(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

"SSH client mock was not invoked which means that there was no attempt to upload a file.",
);
}
const [[input, remoteFilePath]] = sshClientMock.put.mock.calls;
if (input) {
uploadedFile.fileLines = input.toString().split(END_OF_LINE);
Expand Down
22 changes: 11 additions & 11 deletions sources/packages/backend/libs/utilities/src/date-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import * as dayOfYear from "dayjs/plugin/dayOfYear";
import * as isBetween from "dayjs/plugin/isBetween";
import * as customParseFormat from "dayjs/plugin/customParseFormat";
import * as isSameOrAfter from "dayjs/plugin/isSameOrAfter";
import { Between, FindOperator } from "typeorm";
import { And, FindOperator, LessThan, MoreThanOrEqual } from "typeorm";
dayjs.extend(utc);
dayjs.extend(localizedFormat);
dayjs.extend(timezone);
Expand Down Expand Up @@ -201,17 +201,17 @@ export function addToDateOnlyString(
}

/**
* @param date in which the search has to happen
* from midnight to next day midnight.
* @returns typeorm findOperator object between the date.
* Creates the find operator to filter an entire day from the
* midnight(inclusive) till the next day midnight (exclusive).
* @param date day to be selected.
* @returns typeorm {@see FindOperator} to execute the select.
*/
export const dateEqualTo = (date?: Date): FindOperator<Date> => {
// TODO method can be updated when typeorm updates the AND operator in the where clause.
return Between(
new Date(date.setHours(0, 0, 0, 0)),
new Date(date.setDate(date.getDate() + 1)),
);
};
export function dateEqualTo(date: Date): FindOperator<Date> {
const startDate = new Date(date);
startDate.setHours(0, 0, 0, 0);
const endDate = addDays(1, startDate);
return And(MoreThanOrEqual(startDate), LessThan(endDate));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

/**
* Return a PST timestamp with date and time in continuous format
Expand Down