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
5 changes: 5 additions & 0 deletions unicorn_approvals/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ module.exports = {
testEnvironment: "node",
testSequencer: "./tests/alphabetical-sequencer.js",
coverageProvider: "v8",
coverageThreshold: {
global: {
lines: 80,
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { lambdaHandler } from '../../src/approvals_service/contractStatusChanged
import { mockClient } from 'aws-sdk-client-mock';
import {
DynamoDBClient,
UpdateItemCommand,
UpdateItemCommandInput,
} from '@aws-sdk/client-dynamodb';

Expand Down Expand Up @@ -61,4 +62,62 @@ describe('Unit tests for contract creation', function () {

await lambdaHandler(event, context);
});

test('should handle malformed event gracefully', async () => {
ddbMock.on(UpdateItemCommand).resolves({
$metadata: { httpStatusCode: 200 },
});

const expectedId = randomUUID();
const context: Context = {
awsRequestId: expectedId,
} as any;

// Event with missing detail fields - Marshaller will produce undefined values
const event: EventBridgeEvent<string, any> = {
id: expectedId,
account: 'nullAccount',
version: '0',
time: 'nulltime',
region: 'ap-southeast-2',
source: 'unicorn-approvals',
resources: [''],
detail: {},
'detail-type': 'ContractStatusChanged',
};

// The handler catches errors internally and does not rethrow
await expect(lambdaHandler(event, context)).resolves.toBeUndefined();
});

test('should handle DDB failure gracefully', async () => {
ddbMock
.on(UpdateItemCommand)
.rejects(new Error('DynamoDB service unavailable'));

const dateToCheck = new Date();
const expectedId = randomUUID();
const context: Context = {
awsRequestId: expectedId,
} as any;
const event: EventBridgeEvent<string, any> = {
id: expectedId,
account: 'nullAccount',
version: '0',
time: 'nulltime',
region: 'ap-southeast-2',
source: 'unicorn-approvals',
resources: [''],
detail: {
contract_id: 'contract1',
property_id: 'property1',
contract_status: 'APPROVED',
contract_last_modified_on: dateToCheck.toISOString(),
},
'detail-type': 'ContractStatusChanged',
};

// The handler catches errors internally and does not rethrow
await expect(lambdaHandler(event, context)).resolves.toBeUndefined();
});
});
123 changes: 6 additions & 117 deletions unicorn_approvals/tests/unit/propertiesApprovalSyncFunction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,31 +108,6 @@ describe('Unit tests for contract creation', function () {
expect(response.batchItemFailures.length).toEqual(0);
});

test('verifies non-status update check', async () => {
baselineDynamoDBEvent.Records[0].dynamodb.OldImage.contract_id.S =
'oldcontract1';
baselineDynamoDBEvent.Records[0].dynamodb.NewImage.contract_status.S =
'Draft';

function verifyTaskSend(input: any) {
fail(`Unexpected call to SFN with input: ${JSON.stringify(input)}`);
}

sfnMock.callsFake(verifyTaskSend);

const expectedId = randomUUID();
const context: Context = {
awsRequestId: expectedId,
} as any;

const response: DynamoDBBatchResponse = await lambdaHandler(
baselineDynamoDBEvent,
context
);
// Expect no errors.
expect(response.batchItemFailures.length).toEqual(0);
});

test('verifies no task token check', async () => {
const noTaskTokenEvent = {
Records: [
Expand Down Expand Up @@ -187,8 +162,8 @@ describe('Unit tests for contract creation', function () {
expect(response.batchItemFailures.length).toEqual(0);
});

test('verifies approved record update', async () => {
const noTaskTokenEvent = {
test('verifies missing NewImage is skipped', async () => {
const missingNewImageEvent = {
Records: [
{
eventID: 'eventID1',
Expand All @@ -202,23 +177,9 @@ describe('Unit tests for contract creation', function () {
S: 'PROPERTY/australia#sydney/high#23',
},
},
NewImage: {
sfn_wait_approved_task_token: {
S: 'taskToken1',
},
contract_status: {
S: 'APPROVED',
},
contract_id: {
S: 'contractId1',
},
property_id: {
S: 'PROPERTY/australia#sydney/high#23',
},
},
OldImage: {
contract_status: {
S: 'APPROVED',
S: 'DRAFT',
},
contract_id: {
S: 'contractId1',
Expand All @@ -237,8 +198,7 @@ describe('Unit tests for contract creation', function () {

function verifyTaskSend(input: any) {
const cmd = input as SendTaskSuccessCommandInput;
const taskToken = cmd.taskToken;
expect(taskToken).toEqual('taskToken1');
fail(`Unexpected call to SFN with token: ${cmd.taskToken}`);
}

sfnMock.callsFake(verifyTaskSend);
Expand All @@ -249,82 +209,11 @@ describe('Unit tests for contract creation', function () {
} as any;

const response: DynamoDBBatchResponse = await lambdaHandler(
noTaskTokenEvent,
missingNewImageEvent,
context
);
// Expect no errors.
// Expect no errors - record should be skipped
expect(response.batchItemFailures.length).toEqual(0);
});

test('verifies approved record update with an old task token', async () => {
const noTaskTokenEvent = {
Records: [
{
eventID: 'eventID1',
eventVersion: '1.1',
eventSource: 'aws:dynamodb',
awsRegion: 'ap-southeast-2',
dynamodb: {
ApproximateCreationDateTime: 1660484629,
Keys: {
property_id: {
S: 'PROPERTY/australia#sydney/high#23',
},
},
NewImage: {
sfn_wait_approved_task_token: {
S: 'taskToken1',
},
contract_status: {
S: 'APPROVED',
},
contract_id: {
S: 'contractId1',
},
property_id: {
S: 'PROPERTY/australia#sydney/high#23',
},
},
OldImage: {
sfn_wait_approved_task_token: {
S: 'taskToken0',
},
contract_status: {
S: 'APPROVED',
},
contract_id: {
S: 'contractId1',
},
property_id: {
S: 'PROPERTY/australia#sydney/high#23',
},
},
SequenceNumber: '17970100000000005135132811',
SizeBytes: 825,
},
eventSourceARN: 'contractStatusTableARN',
},
],
};

function verifyTaskSend(input: any) {
const cmd = input as SendTaskSuccessCommandInput;
const taskToken = cmd.taskToken;
expect(taskToken).toEqual('taskToken1');
}

sfnMock.callsFake(verifyTaskSend);

const expectedId = randomUUID();
const context: Context = {
awsRequestId: expectedId,
} as any;

const response: DynamoDBBatchResponse = await lambdaHandler(
noTaskTokenEvent,
context
);
// Expect no errors.
expect(response.batchItemFailures.length).toEqual(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { lambdaHandler } from '../../src/approvals_service/waitForContractApprov
import { mockClient } from 'aws-sdk-client-mock';
import {
DynamoDBClient,
GetItemCommand,
GetItemCommandInput,
UpdateItemCommandInput,
} from '@aws-sdk/client-dynamodb';
Expand Down Expand Up @@ -95,7 +96,7 @@ describe('Unit tests for contract status checking', function () {
expect(response.statusCode).toEqual(200);
});

test('verifies unapproved check', async () => {
test('verifies no contract check', async () => {
function verifyGet(input: any) {
try {
const cmd = (input as GetItemCommandInput) ?? {};
Expand All @@ -107,11 +108,6 @@ describe('Unit tests for contract status checking', function () {
$metadata: {
httpStatusCode: 200,
},
Item: {
contract_id: { S: 'contract1' },
property_id: { S: 'PROPERTY/australia#sydney/low#23' },
contract_status: { S: 'DRAFT' },
},
};
} catch (error: any) {
fail(error);
Expand Down Expand Up @@ -161,64 +157,17 @@ describe('Unit tests for contract status checking', function () {
expect(response.statusCode).toEqual(200);
});

test('verifies no contract check', async () => {
function verifyGet(input: any) {
try {
const cmd = (input as GetItemCommandInput) ?? {};
const key = cmd['Key'] ?? {};
expect(key['property_id'].S).toEqual(
'PROPERTY/australia#sydney/low#23'
);
return {
$metadata: {
httpStatusCode: 200,
},
};
} catch (error: any) {
fail(error);
}
}

function verifyUpdate(input: any) {
try {
const cmd = input as UpdateItemCommandInput;
const key = cmd.Key ?? {};
const expressionAttributeValues = cmd.ExpressionAttributeValues ?? {};
expect(key['property_id'].S).toEqual(
'PROPERTY/australia#sydney/low#23'
);
expect(expressionAttributeValues[':t'].S).toEqual('tasktoken1');
return {
$metadata: {
httpStatusCode: 200,
},
};
} catch (error: any) {
fail(error);
}
}
ddbMock.callsFakeOnce(verifyGet).callsFakeOnce(verifyUpdate);
test('verifies DDB failure returns 500', async () => {
ddbMock
.on(GetItemCommand)
.rejects(new Error('DynamoDB service unavailable'));

const expectedId = randomUUID();
const context: Context = {
awsRequestId: expectedId,
} as any;

const response = await lambdaHandler(baselineStepFunctionEvent, context);
const expectedBody = JSON.stringify({
property_id: 'PROPERTY/australia#sydney/low#23',
country: 'Australia',
city: 'Sydney',
street: 'Low',
propertyNumber: '23',
description: 'First property',
contract_id: 'contract1',
listPrice: 23422222,
currency: 'AUD',
images: 's3://filepath',
propertyStatus: 'NEW',
});
expect(response.body).toEqual(expectedBody);
expect(response.statusCode).toEqual(200);
expect(response.statusCode).toEqual(500);
});
});
7 changes: 6 additions & 1 deletion unicorn_contracts/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,10 @@ module.exports = {
testPathIgnorePatterns: ["/node_modules/"],
testEnvironment: "node",
testSequencer: "./tests/alphabetical-sequencer.js",
coverageProvider: "v8"
coverageProvider: "v8",
coverageThreshold: {
global: {
lines: 80,
},
},
};
Loading
Loading