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

#3260 - CAS Integration 3A - Create new Supplier and Site - CAS API Call #3804

Merged
merged 18 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 12 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
9 changes: 9 additions & 0 deletions sims.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"cSpell.words": [
"addressline",
"AEST",
"bcag",
"BCLM",
Expand All @@ -110,16 +111,24 @@
"ESDC",
"Formio",
"golevelup",
"lastupdated",
"MBAL",
"MSFAA",
"NOAAPI",
"NSLSC",
"Overaward",
"overawards",
"PEDU",
"postalcode",
"SABC",
"sbsd",
"SFAS",
"siteprotected",
"supplieraddress",
"suppliername",
"suppliernumber",
"supplierprotected",
"suppliersitecode",
"timestamptz",
"typeorm",
"unparse",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { COUNTRY_CANADA, OTHER_COUNTRY } from "@sims/utilities";
import { IsNotEmpty, IsOptional, ValidateIf } from "class-validator";
import { COUNTRY_CANADA, OTHER_COUNTRY } from "../utils/address-utils";

/**
* Common DTO for Address.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { AddressInfo } from "@sims/sims-db";
import { AddressDetailsAPIOutDTO } from "../models/common.dto";
// 'selectedCountry' in the form will have the value 'other',
// when 'Other'(i.e country other than canada) is selected.
export const OTHER_COUNTRY = "other";
// 'selectedCountry' in the form will have the value 'canada',
// when 'Canada' is selected.
export const COUNTRY_CANADA = "canada";
import { OTHER_COUNTRY } from "@sims/utilities";

/**
* Util to transform address details for formIO.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
import { INestApplication } from "@nestjs/common";
import {
createE2EDataSources,
createFakeUser,
E2EDataSources,
saveFakeCASSupplier,
saveFakeStudent,
} from "@sims/test-utils";
import { QueueNames } from "@sims/utilities";
import { COUNTRY_CANADA, OTHER_COUNTRY, QueueNames } from "@sims/utilities";
import { CASSupplierIntegrationScheduler } from "../cas-supplier-integration.scheduler";
import {
createTestingAppModule,
describeProcessorRootTest,
mockBullJob,
} from "../../../../../test/helpers";
import { SupplierStatus } from "@sims/sims-db";
import { ContactInfo, SupplierStatus } from "@sims/sims-db";
import {
CAS_LOGON_MOCKED_RESULT,
resetCASServiceMock,
SUPPLIER_INFO_FROM_CAS_MOCKED_RESULT,
} from "../../../../../test/helpers/mock-utils/cas-service.mock";
import { CASService } from "@sims/integrations/cas/cas.service";
import {
CASEvaluationStatus,
NotFoundReason,
PreValidationsFailedReason,
} from "../../../../services/cas-supplier/cas-supplier.models";
import {
createFakeCASCreateSupplierAndSiteResponse,
createFakeCASNotFoundSupplierResponse,
} from "../../../../../test/helpers/mock-utils/cas-response.factory";
import { SystemUsersService } from "@sims/services";

describe(describeProcessorRootTest(QueueNames.CASSupplierIntegration), () => {
let app: INestApplication;
let processor: CASSupplierIntegrationScheduler;
let db: E2EDataSources;
let casServiceMock: CASService;
let systemUsersService: SystemUsersService;
const [supplierMockedResult] = SUPPLIER_INFO_FROM_CAS_MOCKED_RESULT.items;

beforeAll(async () => {
Expand All @@ -32,6 +46,7 @@ describe(describeProcessorRootTest(QueueNames.CASSupplierIntegration), () => {
casServiceMock: casServiceMockFromAppModule,
} = await createTestingAppModule();
app = nestApplication;
systemUsersService = nestApplication.get(SystemUsersService);
db = createE2EDataSources(dataSource);
casServiceMock = casServiceMockFromAppModule;
// Processor under test.
Expand All @@ -40,6 +55,14 @@ describe(describeProcessorRootTest(QueueNames.CASSupplierIntegration), () => {

beforeEach(async () => {
jest.clearAllMocks();
resetCASServiceMock(casServiceMock);
// Update existing records to avoid conflicts between tests.
await db.casSupplier.update(
{
supplierStatus: SupplierStatus.PendingSupplierVerification,
},
{ supplierStatus: SupplierStatus.VerifiedManually },
);
});

it("Should finalize CAS supplier process with success when no supplier found.", async () => {
Expand All @@ -66,9 +89,25 @@ describe(describeProcessorRootTest(QueueNames.CASSupplierIntegration), () => {
expect(casServiceMock.getSupplierInfoFromCAS).not.toHaveBeenCalled();
});

it("Should update CAS supplier table when found pending supplier information to be updated.", async () => {
const savedCASSupplier = await saveFakeCASSupplier(db);
const student = savedCASSupplier.student;
it("Should update CAS supplier table when found pending supplier information to be updated with an active address match.", async () => {
// Arrange
// Created a student with same address line 1 and postal code from the expected CAS mocked result.
// Postal code has an white space that is expected to be removed.
const student = await saveFakeStudent(db.dataSource, undefined, {
initialValue: {
contactInfo: {
address: {
addressLine1: "3350 DOUGLAS ST",
city: "Victoria",
country: "Canada",
selectedCountry: COUNTRY_CANADA,
provinceState: "BC",
postalCode: "V8Z 7X9",
},
} as ContactInfo,
},
});
const savedCASSupplier = await saveFakeCASSupplier(db, { student });

// Queued job.
const mockedJob = mockBullJob<void>();
Expand All @@ -86,9 +125,10 @@ describe(describeProcessorRootTest(QueueNames.CASSupplierIntegration), () => {
mockedJob.containLogMessages([
"Found 1 records to be updated.",
"Logon successful.",
`Requesting info for CAS supplier id ${savedCASSupplier.id}.`,
"Updating CAS supplier table.",
"CAS supplier integration executed.",
`Processing student CAS supplier ID: ${savedCASSupplier.id}.`,
`CAS evaluation result status: ${CASEvaluationStatus.ActiveSupplierFound}.`,
"Active CAS supplier found.",
"Updated CAS supplier for the student.",
]),
).toBe(true);

Expand Down Expand Up @@ -121,4 +161,206 @@ describe(describeProcessorRootTest(QueueNames.CASSupplierIntegration), () => {
supplierMockedResult.suppliername,
);
});

it("Should update CAS supplier table to manual intervention when student does not have a first name.", async () => {
// Arrange
const user = createFakeUser();
user.firstName = null;
const student = await saveFakeStudent(db.dataSource, { user });
const savedCASSupplier = await saveFakeCASSupplier(db, { student });

// Queued job.
const mockedJob = mockBullJob<void>();

// Act
const result = await processor.processCASSupplierInformation(mockedJob.job);

// Assert
expect(result).toStrictEqual([
"Process finalized with success.",
"Pending suppliers to update found: 1.",
"Records updated: 1.",
"Attention, process finalized with success but some errors and/or warnings messages may require some attention.",
"Error(s): 0, Warning(s): 1, Info: 12",
]);
expect(
mockedJob.containLogMessages([
"Found 1 records to be updated.",
"Logon successful.",
`Not possible to retrieve CAS supplier information because some pre-validations were not fulfilled. Reason(s): ${PreValidationsFailedReason.GivenNamesNotPresent}.`,
]),
).toBe(true);
// Assert the API methods were not called.
expect(casServiceMock.getSupplierInfoFromCAS).not.toHaveBeenCalled();
// Assert DB was updated.
const updateCASSupplier = await db.casSupplier.findOne({
select: {
id: true,
isValid: true,
supplierStatus: true,
},
where: {
id: savedCASSupplier.id,
},
});
expect(updateCASSupplier).toEqual({
id: savedCASSupplier.id,
isValid: false,
supplierStatus: SupplierStatus.ManualIntervention,
});
});

it("Should update CAS supplier table to manual intervention when student address is not from Canada.", async () => {
// Arrange
const student = await saveFakeStudent(db.dataSource, undefined, {
initialValue: {
contactInfo: {
address: {
selectedCountry: OTHER_COUNTRY,
},
} as ContactInfo,
},
});
const savedCASSupplier = await saveFakeCASSupplier(db, { student });

// Queued job.
const mockedJob = mockBullJob<void>();

// Act
const result = await processor.processCASSupplierInformation(mockedJob.job);

// Assert
expect(result).toStrictEqual([
"Process finalized with success.",
"Pending suppliers to update found: 1.",
"Records updated: 1.",
"Attention, process finalized with success but some errors and/or warnings messages may require some attention.",
"Error(s): 0, Warning(s): 1, Info: 12",
]);
expect(
mockedJob.containLogMessages([
"Found 1 records to be updated.",
"Logon successful.",
`Not possible to retrieve CAS supplier information because some pre-validations were not fulfilled. Reason(s): ${PreValidationsFailedReason.NonCanadianAddress}.`,
]),
).toBe(true);
// Assert the API methods were not called.
expect(casServiceMock.getSupplierInfoFromCAS).not.toHaveBeenCalled();
// Assert DB was updated.
const updateCASSupplier = await db.casSupplier.findOne({
select: {
id: true,
isValid: true,
supplierStatus: true,
},
where: {
id: savedCASSupplier.id,
},
});
expect(updateCASSupplier).toEqual({
id: savedCASSupplier.id,
isValid: false,
supplierStatus: SupplierStatus.ManualIntervention,
});
});

it(
"Should create a new supplier and site on CAS and update CAS suppliers table when " +
"the student was not found on CAS and the request to create the supplier and site was successful.",
async () => {
// Arrange
const referenceDate = new Date();
const savedCASSupplier = await saveFakeCASSupplier(db);
// Configure CAS mock to return em empty result for the GetSupplier
Copy link
Collaborator

Choose a reason for hiding this comment

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

Minor: an

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed.

// and a successful result for the CreateSupplierAndSite.
casServiceMock.getSupplierInfoFromCAS = jest.fn(() =>
Promise.resolve(createFakeCASNotFoundSupplierResponse()),
);
const createSupplierAndSiteResponse =
createFakeCASCreateSupplierAndSiteResponse();
casServiceMock.createSupplierAndSite = jest.fn(() =>
Promise.resolve(createSupplierAndSiteResponse),
);

// Queued job.
const mockedJob = mockBullJob<void>();

// Act
const result = await processor.processCASSupplierInformation(
mockedJob.job,
);

// Assert
expect(result).toStrictEqual([
"Process finalized with success.",
"Pending suppliers to update found: 1.",
"Records updated: 1.",
]);
expect(
mockedJob.containLogMessages([
"Found 1 records to be updated.",
"Logon successful.",
`Processing student CAS supplier ID: ${savedCASSupplier.id}.`,
`CAS evaluation result status: ${CASEvaluationStatus.NotFound}.`,
`No active CAS supplier found. Reason: ${NotFoundReason.SupplierNotFound}.`,
"Created supplier and site on CAS.",
"Updated CAS supplier and site for the student.",
]),
).toBe(true);
// Assert the API methods were called.
expect(casServiceMock.getSupplierInfoFromCAS).toHaveBeenCalled();
expect(casServiceMock.createSupplierAndSite).toHaveBeenCalled();
// Assert DB was updated.
const updateCASSupplier = await db.casSupplier.findOne({
select: {
id: true,
supplierNumber: true,
supplierName: true,
status: true,
lastUpdated: true,
supplierAddress: true as unknown,
supplierStatus: true,
supplierStatusUpdatedOn: true,
isValid: true,
updatedAt: true,
modifier: { id: true },
},
relations: {
modifier: true,
},
where: {
id: savedCASSupplier.id,
},
});
const [submittedAddress] =
createSupplierAndSiteResponse.submittedData.SupplierAddress;
expect(updateCASSupplier).toEqual({
id: savedCASSupplier.id,
supplierNumber: createSupplierAndSiteResponse.response.supplierNumber,
supplierName: createSupplierAndSiteResponse.submittedData.SupplierName,
status: "ACTIVE",
lastUpdated: expect.any(Date),
supplierAddress: {
supplierSiteCode:
createSupplierAndSiteResponse.response.supplierSiteCode,
addressLine1: submittedAddress.AddressLine1,
city: submittedAddress.City,
provinceState: submittedAddress.Province,
country: submittedAddress.Country,
postalCode: submittedAddress.PostalCode,
status: "ACTIVE",
lastUpdated: expect.any(String),
},
supplierStatus: SupplierStatus.Verified,
supplierStatusUpdatedOn: expect.any(Date),
isValid: true,
updatedAt: expect.any(Date),
modifier: { id: systemUsersService.systemUser.id },
});
// Ensure updatedAt was updated.
expect(updateCASSupplier.updatedAt.getTime()).toBeGreaterThan(
referenceDate.getTime(),
);
},
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ import {
ApplicationService,
WorkflowEnqueuerService,
StudentFileService,
CASActiveSupplierNotFoundProcessor,
CASPreValidationsProcessor,
CASActiveSupplierFoundProcessor,
} from "./services";
import { SFASIntegrationModule } from "@sims/integrations/sfas-integration";
import { ATBCIntegrationModule } from "@sims/integrations/atbc-integration";
Expand Down Expand Up @@ -150,6 +153,9 @@ import { ObjectStorageService } from "@sims/integrations/object-storage";
ApplicationChangesReportIntegrationScheduler,
StudentApplicationNotificationsScheduler,
SIMSToSFASIntegrationScheduler,
CASActiveSupplierNotFoundProcessor,
CASPreValidationsProcessor,
CASActiveSupplierFoundProcessor,
],
controllers: [HealthController],
})
Expand Down
Loading