From f37e79ea03638b400b87a0fd0e3343ad44d3e1b5 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Mon, 28 Oct 2024 23:58:14 -0600 Subject: [PATCH 1/8] E2E Tests SIMS to SFAS --- ...-to-sfas-integration.scheduler.e2e-spec.ts | 251 ++++++++++++++++++ .../src/sfas-integration/index.ts | 1 + .../sims-to-sfas.processing.service.ts | 2 +- .../src/data-source/e2e-data-source.ts | 3 + .../libs/test-utils/src/factories/student.ts | 27 +- .../libs/test-utils/src/utils/date-utils.ts | 26 ++ sources/packages/backend/package-lock.json | 7 + sources/packages/backend/package.json | 1 + 8 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts diff --git a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts new file mode 100644 index 0000000000..49f1b7fd14 --- /dev/null +++ b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts @@ -0,0 +1,251 @@ +import { DeepMocked } from "@golevelup/ts-jest"; +import MockDate from "mockdate"; +import { INestApplication } from "@nestjs/common"; +import { formatDate, QueueNames } from "@sims/utilities"; +import { + createTestingAppModule, + describeProcessorRootTest, + mockBullJob, +} from "../../../../../test/helpers"; +import { + E2EDataSources, + createE2EDataSources, + createFakeUser, + saveFakeApplicationDisbursements, + saveFakeStudent, +} from "@sims/test-utils"; +import * as Client from "ssh2-sftp-client"; +import { SIMSToSFASIntegrationScheduler } from "../sims-to-sfas-integration.scheduler"; +import { OfferingIntensity, Student } from "@sims/sims-db"; +import { getUploadedFile } from "@sims/test-utils/mocks"; +import { MoreThan } from "typeorm"; +import { addMilliSeconds, addYears } from "@sims/test-utils/utils"; + +describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { + let app: INestApplication; + let processor: SIMSToSFASIntegrationScheduler; + let db: E2EDataSources; + let sftpClientMock: DeepMocked; + const DATE_FORMAT = "YYYYMMDD"; + + beforeAll(async () => { + const { nestApplication, dataSource, sshClientMock } = + await createTestingAppModule(); + app = nestApplication; + db = createE2EDataSources(dataSource); + sftpClientMock = sshClientMock; + // Processor under test. + processor = app.get(SIMSToSFASIntegrationScheduler); + }); + + afterEach(async () => { + MockDate.reset(); + // Reset all SFAS bridge logs. + await db.sfasBridgeLog.delete({ id: MoreThan(0) }); + }); + + it( + "Should generate a SIMS to SFAS bridge file when there is an update on student data" + + " between the most recent bridge file date and the current bridge file execution date.", + async () => { + // Arrange + // Set the start date and end date of the bridge file to be after 10 years + // to ensure that data produced by other tests will not affect the results of this test. + const latestBridgeFileDate = addYears(10); + const simsDataUpdatedDate = addMilliSeconds(100, latestBridgeFileDate); + const mockedCurrentDate = addMilliSeconds(100, simsDataUpdatedDate); + // Create bridge file log. + await db.sfasBridgeLog.insert({ + referenceDate: latestBridgeFileDate, + generatedFileName: "DummyFileName.TXT", + }); + + // Student created with expected first name, last name and more importantly the updated date + // to fall between the most recent bridge file date and the mocked current date. + const student = await createStudentWithExpectedData( + "FakeFirstName", + "FakeLastName", + simsDataUpdatedDate, + ); + // Student has submitted an application. + await saveFakeApplicationDisbursements( + db.dataSource, + { student }, + { offeringIntensity: OfferingIntensity.partTime }, + ); + + // Queued job. + const mockedJob = mockBullJob(); + + // Mock the current date. + MockDate.set(mockedCurrentDate); + + // Expected file name. + const expectedFileName = buildExpectedFileName(mockedCurrentDate); + + // Act + const processingResult = await processor.generateSFASBridgeFile( + mockedJob.job, + ); + + // Assert + // Assert process result. + expect(processingResult).toContain("Process finalized with success."); + expect(processingResult).toContain("Student records sent: 1."); + expect(processingResult).toContain( + `Uploaded file name: ${expectedFileName}.`, + ); + expect( + mockedJob.containLogMessages([ + `Processing data since ${latestBridgeFileDate} until ${mockedCurrentDate}.`, + "Found 1 student(s) with updates.", + `SIMS to SFAS file ${expectedFileName} has been uploaded successfully.`, + `SIMS to SFAS file log has been created with file name ${expectedFileName} and reference date ${mockedCurrentDate}.`, + ]), + ).toBe(true); + const uploadedFile = getUploadedFile(sftpClientMock); + const [header, studentRecord, footer] = uploadedFile.fileLines; + expect(header).toBe(buildHeader(mockedCurrentDate)); + expect(footer).toBe(`999000000001`); + expect(studentRecord).toBe(buildStudentRecord(student)); + // Check the database for creation of SFAS bridge log. + const uploadedFileLog = await db.sfasBridgeLog.findOneBy({ + generatedFileName: expectedFileName, + referenceDate: mockedCurrentDate, + }); + expect(uploadedFileLog.id).toBeGreaterThan(0); + }, + ); + + /** + * Creates a student with expected first name, last name and updated date. + * @param expectedFirstName expected first name. + * @param expectedLastName expected last name. + * @param expectedUpdatedDate expected updated date. + * @param options optional params. + * - `expectedCASDetails` expected CAS details. + * @returns created student. + */ + async function createStudentWithExpectedData( + expectedFirstName: string, + expectedLastName: string, + expectedUpdatedDate: Date, + options?: { + expectedCASDetails?: { supplierNumber: string; supplierSiteCode: string }; + }, + ): Promise { + const user = createFakeUser(); + user.firstName = expectedFirstName; + user.lastName = expectedLastName; + // Create student with expected first name, last name and updated date. + const student = await saveFakeStudent( + db.dataSource, + { user }, + { initialValue: { updatedAt: expectedUpdatedDate } }, + ); + // Set the student profile updated date to fall between the most recent bridge file date and the mocked current date. + // The updated date if set externally during the creation of the student + // is not being updated by typeorm with the external value. + await db.student.update( + { id: student.id }, + { updatedAt: expectedUpdatedDate }, + ); + // Update CAS details as expected. + await db.casSupplier.update( + { id: student.casSupplier.id }, + { + supplierNumber: options?.expectedCASDetails?.supplierNumber ?? null, + supplierAddress: { + supplierSiteCode: + options?.expectedCASDetails?.supplierSiteCode ?? null, + }, + }, + ); + return student; + } + + it( + "Should not generate a SIMS to SFAS bridge file when there is an update on student data but the student " + + " does not have any submitted application.", + async () => { + // Arrange + // Set the start date and end date of the bridge file to be after 10 years + // to ensure that data produced by other tests will not affect the results of this test. + const latestBridgeFileDate = addYears(10); + const simsDataUpdatedDate = addMilliSeconds(100, latestBridgeFileDate); + const mockedCurrentDate = addMilliSeconds(100, simsDataUpdatedDate); + // Create bridge file log. + await db.sfasBridgeLog.insert({ + referenceDate: latestBridgeFileDate, + generatedFileName: "DummyFileName.TXT", + }); + + // Student created with expected first name, last name and more importantly the updated date + // to fall between the most recent bridge file date and the mocked current date. + await createStudentWithExpectedData( + "FakeFirstName", + "FakeLastName", + simsDataUpdatedDate, + ); + + // Queued job. + const mockedJob = mockBullJob(); + + // Mock the current date. + MockDate.set(mockedCurrentDate); + + // Act + const processingResult = await processor.generateSFASBridgeFile( + mockedJob.job, + ); + + // Assert + // Assert process result. + expect(processingResult).toContain("Process finalized with success."); + expect(processingResult).toContain("Student records sent: 0."); + expect(processingResult).toContain(`Uploaded file name: none.`); + expect( + mockedJob.containLogMessages([ + "There is no SIMS to SFAS updates to process.", + ]), + ).toBe(true); + }, + ); + /** + * Build expected file name. + * @param bridgeFileExtractedDate bridge file extracted date. + * @returns expected file name. + */ + function buildExpectedFileName(bridgeFileExtractedDate: Date) { + return `SIMS-TO-SFAS-${formatDate( + bridgeFileExtractedDate, + "YYYYMMDD-HHmmss", + )}.TXT`; + } + + /** + * Build header. + * @param bridgeFileExtractedDate bridge file extracted date. + * @returns header. + */ + function buildHeader(bridgeFileExtractedDate: Date) { + const creationDate = formatDate(bridgeFileExtractedDate, "YYYYMMDDHHmmss"); + return `100PSFSSIMS to SFAS BRIDGE ${creationDate}`; + } + + /** + * Build student record. + * @param student student. + * @returns student record. + */ + function buildStudentRecord(student: Student): string { + return `200${student.id + .toString() + .padStart(10, "0")}FakeFirstName FakeLastName ${formatDate( + student.birthDate, + DATE_FORMAT, + )}${ + student.sinValidation.sin + }N N 000000000000000000000000000000`; + } +}); diff --git a/sources/packages/backend/libs/integrations/src/sfas-integration/index.ts b/sources/packages/backend/libs/integrations/src/sfas-integration/index.ts index 1c1e9c868f..5a8e447df0 100644 --- a/sources/packages/backend/libs/integrations/src/sfas-integration/index.ts +++ b/sources/packages/backend/libs/integrations/src/sfas-integration/index.ts @@ -1,3 +1,4 @@ +export * from "./sfas-integration.models"; export * from "./sfas-integration.processing.service"; export * from "./sims-to-sfas.integration.service"; export * from "./sims-to-sfas.processing.service"; diff --git a/sources/packages/backend/libs/integrations/src/sfas-integration/sims-to-sfas.processing.service.ts b/sources/packages/backend/libs/integrations/src/sfas-integration/sims-to-sfas.processing.service.ts index f9e3b140b5..e2d5cac07e 100644 --- a/sources/packages/backend/libs/integrations/src/sfas-integration/sims-to-sfas.processing.service.ts +++ b/sources/packages/backend/libs/integrations/src/sfas-integration/sims-to-sfas.processing.service.ts @@ -65,7 +65,7 @@ export class SIMSToSFASProcessingService { } processSummary.info( - `Found ${uniqueStudentIds.length} students with updates.`, + `Found ${uniqueStudentIds.length} student(s) with updates.`, ); const studentDetails = await this.simsToSFASService.getStudentRecordsByStudentIds( diff --git a/sources/packages/backend/libs/test-utils/src/data-source/e2e-data-source.ts b/sources/packages/backend/libs/test-utils/src/data-source/e2e-data-source.ts index 7331c7b24f..9c2468cfb7 100644 --- a/sources/packages/backend/libs/test-utils/src/data-source/e2e-data-source.ts +++ b/sources/packages/backend/libs/test-utils/src/data-source/e2e-data-source.ts @@ -55,6 +55,7 @@ import { CASSupplier, ApplicationRestrictionBypass, BetaUsersAuthorizations, + SFASBridgeLog, } from "@sims/sims-db"; import { DataSource, Repository } from "typeorm"; @@ -147,6 +148,7 @@ export function createE2EDataSources(dataSource: DataSource): E2EDataSources { ), studentLoanBalance: dataSource.getRepository(StudentLoanBalance), eCertFeedbackError: dataSource.getRepository(ECertFeedbackError), + sfasBridgeLog: dataSource.getRepository(SFASBridgeLog), }; } @@ -211,4 +213,5 @@ export interface E2EDataSources { applicationOfferingChangeRequest: Repository; studentLoanBalance: Repository; eCertFeedbackError: Repository; + sfasBridgeLog: Repository; } diff --git a/sources/packages/backend/libs/test-utils/src/factories/student.ts b/sources/packages/backend/libs/test-utils/src/factories/student.ts index a608e188e6..79ee612fb5 100644 --- a/sources/packages/backend/libs/test-utils/src/factories/student.ts +++ b/sources/packages/backend/libs/test-utils/src/factories/student.ts @@ -1,6 +1,13 @@ import * as faker from "faker"; -import { DisabilityStatus, SINValidation, Student, User } from "@sims/sims-db"; -import { createFakeUser } from "@sims/test-utils"; +import { + CASSupplier, + DisabilityStatus, + SINValidation, + Student, + SupplierStatus, + User, +} from "@sims/sims-db"; +import { createFakeCASSupplier, createFakeUser } from "@sims/test-utils"; import { DataSource } from "typeorm"; import { createFakeSINValidation } from "./sin-validation"; import { COUNTRY_CANADA, getISODateOnlyString } from "@sims/utilities"; @@ -48,7 +55,12 @@ export function createFakeStudent( */ export async function saveFakeStudent( dataSource: DataSource, - relations?: { student?: Student; user?: User; sinValidation?: SINValidation }, + relations?: { + student?: Student; + user?: User; + sinValidation?: SINValidation; + casSupplier?: CASSupplier; + }, options?: { initialValue?: Partial; sinValidationInitialValue?: Partial; @@ -68,5 +80,14 @@ export async function saveFakeStudent( { student }, { initialValue: options?.sinValidationInitialValue }, ); + // Save CAS Supplier. + student.casSupplier = + relations?.casSupplier ?? + createFakeCASSupplier( + { student, auditUser: student.user }, + { + supplierStatus: SupplierStatus.Verified, + }, + ); return studentRepo.save(student); } diff --git a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts index d4c1fd6544..25425f0401 100644 --- a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts +++ b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts @@ -8,3 +8,29 @@ import * as dayjs from "dayjs"; export function isValidFileTimestamp(timestamp: string): boolean { return dayjs(timestamp, "YYYYMMDD-HHmmssSSS").isValid(); } +/** + * Add years to a given date. + * @param yearsToAdd number of years to be added. + * @param date date. + * @returns a new date with years added. + */ +export const addYears = (yearsToAdd: number, date?: Date | string): Date => { + return dayjs(date ? date : new Date()) + .add(yearsToAdd, "year") + .toDate(); +}; + +/** + * Add milliseconds to a given date. + * @param yearsToAdd number of years to be added. + * @param date date. + * @returns a new date with years added. + */ +export const addMilliSeconds = ( + milliSecondsToAdd: number, + date?: Date | string, +): Date => { + return dayjs(date ? date : new Date()) + .add(milliSecondsToAdd, "millisecond") + .toDate(); +}; diff --git a/sources/packages/backend/package-lock.json b/sources/packages/backend/package-lock.json index 5887635319..e55ff9216e 100644 --- a/sources/packages/backend/package-lock.json +++ b/sources/packages/backend/package-lock.json @@ -85,6 +85,7 @@ "faker": "^5.2.0", "find-config": "^1.0.0", "jest": "^29.6.4", + "mockdate": "^3.0.5", "prettier": "^2.1.2", "supertest": "^6.3.3", "ts-jest": "^29.1.1", @@ -10316,6 +10317,12 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mockdate": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz", + "integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==", + "dev": true + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/sources/packages/backend/package.json b/sources/packages/backend/package.json index 1409ad9308..ea79d0f7ac 100644 --- a/sources/packages/backend/package.json +++ b/sources/packages/backend/package.json @@ -118,6 +118,7 @@ "faker": "^5.2.0", "find-config": "^1.0.0", "jest": "^29.6.4", + "mockdate": "^3.0.5", "prettier": "^2.1.2", "supertest": "^6.3.3", "ts-jest": "^29.1.1", From d35b8d74d8363f78e265e0f87fb1ebe804f984b0 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 29 Oct 2024 00:05:23 -0600 Subject: [PATCH 2/8] Sonar Cloud refactor --- .../packages/backend/libs/test-utils/src/utils/date-utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts index 25425f0401..8a5f0b1d18 100644 --- a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts +++ b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts @@ -15,7 +15,7 @@ export function isValidFileTimestamp(timestamp: string): boolean { * @returns a new date with years added. */ export const addYears = (yearsToAdd: number, date?: Date | string): Date => { - return dayjs(date ? date : new Date()) + return dayjs(date ?? new Date()) .add(yearsToAdd, "year") .toDate(); }; @@ -30,7 +30,7 @@ export const addMilliSeconds = ( milliSecondsToAdd: number, date?: Date | string, ): Date => { - return dayjs(date ? date : new Date()) + return dayjs(date ?? new Date()) .add(milliSecondsToAdd, "millisecond") .toDate(); }; From 99dc926ad685f532e168009ab545bd8532a11297 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 29 Oct 2024 01:03:18 -0600 Subject: [PATCH 3/8] Adjustments to create CAS supplier --- ...-to-sfas-integration.scheduler.e2e-spec.ts | 134 +++++++++--------- .../libs/test-utils/src/factories/student.ts | 21 +-- .../libs/test-utils/src/utils/date-utils.ts | 1 + 3 files changed, 67 insertions(+), 89 deletions(-) diff --git a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts index 49f1b7fd14..b8b2dd972c 100644 --- a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts +++ b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts @@ -1,5 +1,6 @@ import { DeepMocked } from "@golevelup/ts-jest"; import MockDate from "mockdate"; +import * as faker from "faker"; import { INestApplication } from "@nestjs/common"; import { formatDate, QueueNames } from "@sims/utilities"; import { @@ -10,13 +11,14 @@ import { import { E2EDataSources, createE2EDataSources, + createFakeCASSupplier, createFakeUser, saveFakeApplicationDisbursements, saveFakeStudent, } from "@sims/test-utils"; import * as Client from "ssh2-sftp-client"; import { SIMSToSFASIntegrationScheduler } from "../sims-to-sfas-integration.scheduler"; -import { OfferingIntensity, Student } from "@sims/sims-db"; +import { OfferingIntensity, Student, SupplierStatus } from "@sims/sims-db"; import { getUploadedFile } from "@sims/test-utils/mocks"; import { MoreThan } from "typeorm"; import { addMilliSeconds, addYears } from "@sims/test-utils/utils"; @@ -38,12 +40,16 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { processor = app.get(SIMSToSFASIntegrationScheduler); }); - afterEach(async () => { - MockDate.reset(); + beforeEach(async () => { // Reset all SFAS bridge logs. await db.sfasBridgeLog.delete({ id: MoreThan(0) }); }); + afterEach(async () => { + // Reset the current date mock. + MockDate.reset(); + }); + it( "Should generate a SIMS to SFAS bridge file when there is an update on student data" + " between the most recent bridge file date and the current bridge file execution date.", @@ -52,8 +58,8 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { // Set the start date and end date of the bridge file to be after 10 years // to ensure that data produced by other tests will not affect the results of this test. const latestBridgeFileDate = addYears(10); - const simsDataUpdatedDate = addMilliSeconds(100, latestBridgeFileDate); - const mockedCurrentDate = addMilliSeconds(100, simsDataUpdatedDate); + const simsDataUpdatedDate = addMilliSeconds(10, latestBridgeFileDate); + const mockedCurrentDate = addMilliSeconds(10, simsDataUpdatedDate); // Create bridge file log. await db.sfasBridgeLog.insert({ referenceDate: latestBridgeFileDate, @@ -62,11 +68,7 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { // Student created with expected first name, last name and more importantly the updated date // to fall between the most recent bridge file date and the mocked current date. - const student = await createStudentWithExpectedData( - "FakeFirstName", - "FakeLastName", - simsDataUpdatedDate, - ); + const student = await createStudentWithExpectedData(simsDataUpdatedDate); // Student has submitted an application. await saveFakeApplicationDisbursements( db.dataSource, @@ -117,53 +119,6 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { }, ); - /** - * Creates a student with expected first name, last name and updated date. - * @param expectedFirstName expected first name. - * @param expectedLastName expected last name. - * @param expectedUpdatedDate expected updated date. - * @param options optional params. - * - `expectedCASDetails` expected CAS details. - * @returns created student. - */ - async function createStudentWithExpectedData( - expectedFirstName: string, - expectedLastName: string, - expectedUpdatedDate: Date, - options?: { - expectedCASDetails?: { supplierNumber: string; supplierSiteCode: string }; - }, - ): Promise { - const user = createFakeUser(); - user.firstName = expectedFirstName; - user.lastName = expectedLastName; - // Create student with expected first name, last name and updated date. - const student = await saveFakeStudent( - db.dataSource, - { user }, - { initialValue: { updatedAt: expectedUpdatedDate } }, - ); - // Set the student profile updated date to fall between the most recent bridge file date and the mocked current date. - // The updated date if set externally during the creation of the student - // is not being updated by typeorm with the external value. - await db.student.update( - { id: student.id }, - { updatedAt: expectedUpdatedDate }, - ); - // Update CAS details as expected. - await db.casSupplier.update( - { id: student.casSupplier.id }, - { - supplierNumber: options?.expectedCASDetails?.supplierNumber ?? null, - supplierAddress: { - supplierSiteCode: - options?.expectedCASDetails?.supplierSiteCode ?? null, - }, - }, - ); - return student; - } - it( "Should not generate a SIMS to SFAS bridge file when there is an update on student data but the student " + " does not have any submitted application.", @@ -172,8 +127,8 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { // Set the start date and end date of the bridge file to be after 10 years // to ensure that data produced by other tests will not affect the results of this test. const latestBridgeFileDate = addYears(10); - const simsDataUpdatedDate = addMilliSeconds(100, latestBridgeFileDate); - const mockedCurrentDate = addMilliSeconds(100, simsDataUpdatedDate); + const simsDataUpdatedDate = addMilliSeconds(10, latestBridgeFileDate); + const mockedCurrentDate = addMilliSeconds(10, simsDataUpdatedDate); // Create bridge file log. await db.sfasBridgeLog.insert({ referenceDate: latestBridgeFileDate, @@ -182,11 +137,7 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { // Student created with expected first name, last name and more importantly the updated date // to fall between the most recent bridge file date and the mocked current date. - await createStudentWithExpectedData( - "FakeFirstName", - "FakeLastName", - simsDataUpdatedDate, - ); + await createStudentWithExpectedData(simsDataUpdatedDate); // Queued job. const mockedJob = mockBullJob(); @@ -211,6 +162,52 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { ).toBe(true); }, ); + + /** + * Creates a student with expected first name, last name and updated date. + * @param expectedUpdatedDate expected updated date. + * @param options optional params. + * - `expectedCASDetails` expected CAS details. + * @returns created student. + */ + async function createStudentWithExpectedData( + expectedUpdatedDate: Date, + options?: { + expectedCASDetails?: { supplierNumber: string; supplierSiteCode: string }; + }, + ): Promise { + const user = createFakeUser(); + // Name with fixed length will be easy to build the expected file data. + user.firstName = faker.random.alpha({ count: 15 }); + user.lastName = faker.random.alpha({ count: 25 }); + // Create student with expected first name, last name and updated date. + const student = await saveFakeStudent(db.dataSource, { user }); + // Create CAS supplier. + const casSupplier = createFakeCASSupplier( + { + student, + auditUser: student.user, + }, + { + supplierStatus: SupplierStatus.Verified, + }, + ); + // Update CAS details as expected. + casSupplier.supplierNumber = + options?.expectedCASDetails?.supplierNumber ?? null; + casSupplier.supplierAddress.supplierSiteCode = + options?.expectedCASDetails?.supplierSiteCode ?? null; + await db.casSupplier.save(casSupplier); + + // Set the student profile updated date to fall between the most recent bridge file date and the mocked current date. + // Set the CAS supplier. + await db.student.update( + { id: student.id }, + { casSupplier, updatedAt: expectedUpdatedDate }, + ); + return student; + } + /** * Build expected file name. * @param bridgeFileExtractedDate bridge file extracted date. @@ -239,12 +236,9 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { * @returns student record. */ function buildStudentRecord(student: Student): string { - return `200${student.id - .toString() - .padStart(10, "0")}FakeFirstName FakeLastName ${formatDate( - student.birthDate, - DATE_FORMAT, - )}${ + return `200${student.id.toString().padStart(10, "0")}${ + student.user.firstName + }${student.user.lastName}${formatDate(student.birthDate, DATE_FORMAT)}${ student.sinValidation.sin }N N 000000000000000000000000000000`; } diff --git a/sources/packages/backend/libs/test-utils/src/factories/student.ts b/sources/packages/backend/libs/test-utils/src/factories/student.ts index 79ee612fb5..bd301a6389 100644 --- a/sources/packages/backend/libs/test-utils/src/factories/student.ts +++ b/sources/packages/backend/libs/test-utils/src/factories/student.ts @@ -1,13 +1,6 @@ import * as faker from "faker"; -import { - CASSupplier, - DisabilityStatus, - SINValidation, - Student, - SupplierStatus, - User, -} from "@sims/sims-db"; -import { createFakeCASSupplier, createFakeUser } from "@sims/test-utils"; +import { DisabilityStatus, SINValidation, Student, User } from "@sims/sims-db"; +import { createFakeUser } from "@sims/test-utils"; import { DataSource } from "typeorm"; import { createFakeSINValidation } from "./sin-validation"; import { COUNTRY_CANADA, getISODateOnlyString } from "@sims/utilities"; @@ -59,7 +52,6 @@ export async function saveFakeStudent( student?: Student; user?: User; sinValidation?: SINValidation; - casSupplier?: CASSupplier; }, options?: { initialValue?: Partial; @@ -80,14 +72,5 @@ export async function saveFakeStudent( { student }, { initialValue: options?.sinValidationInitialValue }, ); - // Save CAS Supplier. - student.casSupplier = - relations?.casSupplier ?? - createFakeCASSupplier( - { student, auditUser: student.user }, - { - supplierStatus: SupplierStatus.Verified, - }, - ); return studentRepo.save(student); } diff --git a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts index 8a5f0b1d18..1a62cb6987 100644 --- a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts +++ b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts @@ -8,6 +8,7 @@ import * as dayjs from "dayjs"; export function isValidFileTimestamp(timestamp: string): boolean { return dayjs(timestamp, "YYYYMMDD-HHmmssSSS").isValid(); } + /** * Add years to a given date. * @param yearsToAdd number of years to be added. From d4e8b8de7028079fd03cf7fa827d612f060fbec6 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 29 Oct 2024 01:11:41 -0600 Subject: [PATCH 4/8] Refactoring the dates usage --- ...s-to-sfas-integration.scheduler.e2e-spec.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts index b8b2dd972c..7bf5bb1a34 100644 --- a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts +++ b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts @@ -28,6 +28,9 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { let processor: SIMSToSFASIntegrationScheduler; let db: E2EDataSources; let sftpClientMock: DeepMocked; + let latestBridgeFileDate: Date; + let simsDataUpdatedDate: Date; + let mockedCurrentDate: Date; const DATE_FORMAT = "YYYYMMDD"; beforeAll(async () => { @@ -43,6 +46,11 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { beforeEach(async () => { // Reset all SFAS bridge logs. await db.sfasBridgeLog.delete({ id: MoreThan(0) }); + // Set the start date and end date of the bridge file to be after 10 years + // to ensure that data produced by other tests will not affect the results of this test. + latestBridgeFileDate = addYears(10); + simsDataUpdatedDate = addMilliSeconds(10, latestBridgeFileDate); + mockedCurrentDate = addMilliSeconds(10, simsDataUpdatedDate); }); afterEach(async () => { @@ -55,11 +63,6 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { " between the most recent bridge file date and the current bridge file execution date.", async () => { // Arrange - // Set the start date and end date of the bridge file to be after 10 years - // to ensure that data produced by other tests will not affect the results of this test. - const latestBridgeFileDate = addYears(10); - const simsDataUpdatedDate = addMilliSeconds(10, latestBridgeFileDate); - const mockedCurrentDate = addMilliSeconds(10, simsDataUpdatedDate); // Create bridge file log. await db.sfasBridgeLog.insert({ referenceDate: latestBridgeFileDate, @@ -124,11 +127,6 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { " does not have any submitted application.", async () => { // Arrange - // Set the start date and end date of the bridge file to be after 10 years - // to ensure that data produced by other tests will not affect the results of this test. - const latestBridgeFileDate = addYears(10); - const simsDataUpdatedDate = addMilliSeconds(10, latestBridgeFileDate); - const mockedCurrentDate = addMilliSeconds(10, simsDataUpdatedDate); // Create bridge file log. await db.sfasBridgeLog.insert({ referenceDate: latestBridgeFileDate, From 38e2a420b4963cfd81773a1060d1675872ef5701 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 29 Oct 2024 01:25:21 -0600 Subject: [PATCH 5/8] Removing unused code. --- .../backend/libs/integrations/src/sfas-integration/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/sources/packages/backend/libs/integrations/src/sfas-integration/index.ts b/sources/packages/backend/libs/integrations/src/sfas-integration/index.ts index 5a8e447df0..1c1e9c868f 100644 --- a/sources/packages/backend/libs/integrations/src/sfas-integration/index.ts +++ b/sources/packages/backend/libs/integrations/src/sfas-integration/index.ts @@ -1,4 +1,3 @@ -export * from "./sfas-integration.models"; export * from "./sfas-integration.processing.service"; export * from "./sims-to-sfas.integration.service"; export * from "./sims-to-sfas.processing.service"; From 663c5d1f7f0a4e5a94f4da76a7b59410b85327af Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 29 Oct 2024 10:11:10 -0600 Subject: [PATCH 6/8] Adjusted comment and removed backtick where not required. --- .../sims-to-sfas-integration.scheduler.e2e-spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts index 7bf5bb1a34..6820450d39 100644 --- a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts +++ b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts @@ -49,6 +49,10 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { // Set the start date and end date of the bridge file to be after 10 years // to ensure that data produced by other tests will not affect the results of this test. latestBridgeFileDate = addYears(10); + // Set the data updated date and mocked current date in milliseconds later than + // the latest bridge file date to ensure that it holds integrity only for the particular test scenario + // and not having to update the database for each test case + // which could potentially have various columns to be updated. simsDataUpdatedDate = addMilliSeconds(10, latestBridgeFileDate); mockedCurrentDate = addMilliSeconds(10, simsDataUpdatedDate); }); @@ -111,7 +115,7 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { const uploadedFile = getUploadedFile(sftpClientMock); const [header, studentRecord, footer] = uploadedFile.fileLines; expect(header).toBe(buildHeader(mockedCurrentDate)); - expect(footer).toBe(`999000000001`); + expect(footer).toBe("999000000001"); expect(studentRecord).toBe(buildStudentRecord(student)); // Check the database for creation of SFAS bridge log. const uploadedFileLog = await db.sfasBridgeLog.findOneBy({ @@ -152,7 +156,7 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { // Assert process result. expect(processingResult).toContain("Process finalized with success."); expect(processingResult).toContain("Student records sent: 0."); - expect(processingResult).toContain(`Uploaded file name: none.`); + expect(processingResult).toContain("Uploaded file name: none."); expect( mockedJob.containLogMessages([ "There is no SIMS to SFAS updates to process.", From 2c9572902156077c475a679b2ed704e61794f2ca Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 29 Oct 2024 14:19:17 -0600 Subject: [PATCH 7/8] review comments --- ...ims-to-sfas-integration.scheduler.e2e-spec.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts index 6820450d39..6681235418 100644 --- a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts +++ b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts @@ -99,11 +99,11 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { // Assert // Assert process result. - expect(processingResult).toContain("Process finalized with success."); - expect(processingResult).toContain("Student records sent: 1."); - expect(processingResult).toContain( + expect(processingResult).toEqual([ + "Process finalized with success.", + "Student records sent: 1.", `Uploaded file name: ${expectedFileName}.`, - ); + ]); expect( mockedJob.containLogMessages([ `Processing data since ${latestBridgeFileDate} until ${mockedCurrentDate}.`, @@ -154,9 +154,11 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { // Assert // Assert process result. - expect(processingResult).toContain("Process finalized with success."); - expect(processingResult).toContain("Student records sent: 0."); - expect(processingResult).toContain("Uploaded file name: none."); + expect(processingResult).toEqual([ + "Process finalized with success.", + "Student records sent: 0.", + "Uploaded file name: none.", + ]); expect( mockedJob.containLogMessages([ "There is no SIMS to SFAS updates to process.", From 49e817cae104478886436a049c07b1b5370979f6 Mon Sep 17 00:00:00 2001 From: Dheepak Ramanathan Date: Tue, 29 Oct 2024 15:40:09 -0600 Subject: [PATCH 8/8] review comments. --- .../sims-to-sfas-integration.scheduler.e2e-spec.ts | 13 ++++++------- .../backend/libs/test-utils/src/utils/date-utils.ts | 4 ++-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts index 6681235418..d8eb82d977 100644 --- a/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts +++ b/sources/packages/backend/apps/queue-consumers/src/processors/schedulers/sfas-integration/_tests_/sims-to-sfas-integration.scheduler.e2e-spec.ts @@ -20,7 +20,6 @@ import * as Client from "ssh2-sftp-client"; import { SIMSToSFASIntegrationScheduler } from "../sims-to-sfas-integration.scheduler"; import { OfferingIntensity, Student, SupplierStatus } from "@sims/sims-db"; import { getUploadedFile } from "@sims/test-utils/mocks"; -import { MoreThan } from "typeorm"; import { addMilliSeconds, addYears } from "@sims/test-utils/utils"; describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { @@ -45,7 +44,7 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { beforeEach(async () => { // Reset all SFAS bridge logs. - await db.sfasBridgeLog.delete({ id: MoreThan(0) }); + await db.sfasBridgeLog.delete({}); // Set the start date and end date of the bridge file to be after 10 years // to ensure that data produced by other tests will not affect the results of this test. latestBridgeFileDate = addYears(10); @@ -63,8 +62,8 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { }); it( - "Should generate a SIMS to SFAS bridge file when there is an update on student data" + - " between the most recent bridge file date and the current bridge file execution date.", + "Should generate a SIMS to SFAS bridge file when there is an update on student data " + + "between the most recent bridge file date and the current bridge file execution date.", async () => { // Arrange // Create bridge file log. @@ -118,17 +117,17 @@ describe(describeProcessorRootTest(QueueNames.SIMSToSFASIntegration), () => { expect(footer).toBe("999000000001"); expect(studentRecord).toBe(buildStudentRecord(student)); // Check the database for creation of SFAS bridge log. - const uploadedFileLog = await db.sfasBridgeLog.findOneBy({ + const uploadedFileLog = await db.sfasBridgeLog.existsBy({ generatedFileName: expectedFileName, referenceDate: mockedCurrentDate, }); - expect(uploadedFileLog.id).toBeGreaterThan(0); + expect(uploadedFileLog).toBe(true); }, ); it( "Should not generate a SIMS to SFAS bridge file when there is an update on student data but the student " + - " does not have any submitted application.", + "does not have any submitted application.", async () => { // Arrange // Create bridge file log. diff --git a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts index 1a62cb6987..d447e413ec 100644 --- a/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts +++ b/sources/packages/backend/libs/test-utils/src/utils/date-utils.ts @@ -12,7 +12,7 @@ export function isValidFileTimestamp(timestamp: string): boolean { /** * Add years to a given date. * @param yearsToAdd number of years to be added. - * @param date date. + * @param date date. * @returns a new date with years added. */ export const addYears = (yearsToAdd: number, date?: Date | string): Date => { @@ -24,7 +24,7 @@ export const addYears = (yearsToAdd: number, date?: Date | string): Date => { /** * Add milliseconds to a given date. * @param yearsToAdd number of years to be added. - * @param date date. + * @param date date. * @returns a new date with years added. */ export const addMilliSeconds = (