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

Merge Prod Release into Master #26

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
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
22 changes: 21 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
{
"extends": ["@dvsa/eslint-config-ts", "prettier"]
"extends": ["@dvsa/eslint-config-ts", "prettier"],
"settings": {
"import/resolver": {
"typescript": {
"alwaysTryTypes": true
}
}
},
"rules": {
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true
}
],
"max-len":["error", {
"code": 150,
"ignoreComments": true
}],
"@typescript-eslint/no-unsafe-enum-comparison": "off"
}
}
45,954 changes: 16,667 additions & 29,287 deletions package-lock.json

Large diffs are not rendered by default.

85 changes: 45 additions & 40 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"test:integration": "cross-env NODE_ENV=test echo 'integration tests to be added'",
"test:integration:watch": "echo 'integration tests --watch to be added'",
"test:integration:coverage": "cross-env echo 'integration tests coverage to be added'",
"test": "npm-run-all lint test:unit:coverage test:integration:coverage",
"test": "npm-run-all test:unit:coverage test:integration:coverage",
"audit": "npm audit --prod",
"lint:analyse": "eslint . --ext js,ts --fix",
"lint:report": "npm run lint:analyse -- -f json -o reports/eslint/eslint-report.json",
Expand All @@ -40,47 +40,52 @@
"tools-setup": "echo 'nothing to do for now'"
},
"dependencies": {
"aws-lambda": "1.0.6",
"aws-sdk": "2.1354.0",
"dateformat": "4.6.3",
"joi": "^14.3.1",
"luxon": "3.2.1",
"winston": "3.8.2"
"@aws-sdk/client-eventbridge": "^3.577.0",
"@aws-sdk/util-dynamodb": "^3.577.0",
"@dvsa/cvs-type-definitions": "^7.2.0",
"@dvsa/cvs-microservice-common": "1.1.0",
"aws-lambda": "^1.0.7",
"dateformat": "^5.0.3",
"joi": "^17.13.1",
"luxon": "^3.4.4",
"winston": "^3.13.0"
},
"devDependencies": {
"@commitlint/cli": "14.1.0",
"@commitlint/config-conventional": "11.0.0",
"@dvsa/eslint-config-ts": "2.2.0",
"@types/aws-lambda": "8.10.70",
"@types/jest": "27.0.2",
"@types/luxon": "3.0.2",
"@types/node": "14.18.16",
"@types/supertest": "2.0.10",
"@typescript-eslint/eslint-plugin": "5.3.1",
"@typescript-eslint/parser": "5.3.1",
"commitlint-plugin-function-rules": "1.1.20",
"concurrently": "6.3.0",
"cross-env": "7.0.3",
"eslint": "7.32.0",
"eslint-config-prettier": "7.1.0",
"eslint-plugin-jest": "24.3.2",
"eslint-plugin-prettier": "3.3.1",
"husky": "8.0.1",
"jest": "27.3.1",
"npm-run-all": "4.1.5",
"prettier": "2.2.1",
"rimraf": "3.0.2",
"semantic-release": "19.0.3",
"serverless": "3.22.0",
"serverless-offline": "11.0.1",
"serverless-offline-aws-eventbridge": "2.0.3",
"serverless-plugin-typescript": "2.1.5",
"sonar-scanner": "3.1.0",
"supertest": "6.0.1",
"ts-jest": "27.0.7",
"ts-loader": "8.0.14",
"ts-node": "9.1.1",
"typescript": "4.4.4"
"@commitlint/cli": "^19.3.0",
"@commitlint/config-conventional": "^19.2.2",
"@dvsa/eslint-config-ts": "^3.0.1",
"@types/aws-lambda": "^8.10.138",
"@types/jest": "^29.5.12",
"@types/luxon": "^3.4.2",
"@types/node": "^20.12.12",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^7.10.0",
"@typescript-eslint/parser": "^7.10.0",
"aws-sdk-client-mock": "^4.0.0",
"commitlint-plugin-function-rules": "^4.0.0",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jest": "^28.5.0",
"eslint-plugin-prettier": "^5.1.3",
"husky": "^9.0.11",
"jest": "^29.7.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
"rimraf": "^5.0.7",
"semantic-release": "^23.1.1",
"serverless": "^3.22.0",
"serverless-offline": "^13.6.0",
"serverless-offline-aws-eventbridge": "^2.1.0",
"serverless-plugin-typescript": "^2.1.5",
"sonar-scanner": "^3.1.0",
"supertest": "^7.0.0",
"ts-jest": "^29.1.3",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
},
"author": "",
"license": "MIT",
Expand Down
10 changes: 0 additions & 10 deletions src/assets/Enums.ts

This file was deleted.

25 changes: 18 additions & 7 deletions src/eventbridge/Send.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { EventBridge } from 'aws-sdk';
import { EventEntry } from './EventEntry';
import { Entries } from './Entries';
import { SendResponse } from './SendResponse';
/* eslint-disable security/detect-object-injection */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
// eslint-disable-next-line import/no-extraneous-dependencies
import {
EventBridgeClient,
PutEventsCommand,
} from '@aws-sdk/client-eventbridge';
import logger from '../observability/Logger';
import { MCRequest } from '../utils/MCRequest';
import { Entries } from './Entries';
import { EventEntry } from './EventEntry';
import { SendResponse } from './SendResponse';

const eventbridge = new EventBridge();
const sendMCProhibition = async (mcRequests: MCRequest[]): Promise<SendResponse> => {
const eventBridge = new EventBridgeClient();
const sendMCProhibition = async (
mcRequests: MCRequest[],
): Promise<SendResponse> => {
const sendResponse: SendResponse = {
SuccessCount: 0,
FailCount: 0,
Expand All @@ -25,8 +35,9 @@ const sendMCProhibition = async (mcRequests: MCRequest[]): Promise<SendResponse>
Entries: [],
};
params.Entries.push(entry);
const command = new PutEventsCommand(params);
// eslint-disable-next-line no-await-in-loop
await eventbridge.putEvents(params).promise();
await eventBridge.send(command);
sendResponse.SuccessCount++;
}
} catch (error) {
Expand Down
61 changes: 30 additions & 31 deletions src/handler.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,53 @@
/* eslint-disable */

import { DynamoDBStreamEvent, Context, Callback } from 'aws-lambda';
import { Context, Callback, SQSEvent, DynamoDBRecord, SQSBatchItemFailure } from 'aws-lambda';
import { extractMCTestResults } from './utils/ExtractTestResults';
import { sendMCProhibition } from './eventbridge/Send';
import logger from './observability/Logger';
import { MCRequest } from './utils/MCRequest';

const handler = async (
event: DynamoDBStreamEvent,
event: SQSEvent,
_context: Context,
callback: Callback,
_callback: Callback,
) => {
let { NODE_ENV, SERVICE, AWS_REGION, AWS_STAGE, SEND_TO_SMC } = process.env;
const { NODE_ENV, SERVICE, AWS_REGION, AWS_STAGE, SEND_TO_SMC } = process.env;

logger.debug(
`\nRunning Service:\n '${SERVICE}'\n mode: ${NODE_ENV}\n stage: '${AWS_STAGE}'\n region: '${AWS_REGION}'\n
Send to smc: ${SEND_TO_SMC}\n`,
);
if (SEND_TO_SMC != undefined && SEND_TO_SMC.toUpperCase() === 'TRUE') {
try {
logger.debug(`Function triggered with '${JSON.stringify(event)}'.`);

// We want to process these in sequence to maintain order of database changes
for (const record of event.Records) {
const mcRequests: MCRequest[] = extractMCTestResults(record);
if (mcRequests != null) {

const batchItemFailures: SQSBatchItemFailure[] = [];

if (SEND_TO_SMC?.toUpperCase() === 'TRUE') {
logger.debug(`Function triggered with '${JSON.stringify(event)}'.`);

for (const record of event.Records) {
try {
const dynamoDBEvent: DynamoDBRecord = JSON.parse(record.body) as DynamoDBRecord;
const mcRequests: MCRequest[] = extractMCTestResults(dynamoDBEvent);

if (mcRequests.length > 0) {
await sendMCProhibition(mcRequests);
} else {
logger.info(`No relevant MC test results found in the record: ${JSON.stringify(dynamoDBEvent)}`);
}
}
callback(null, 'Data processed successfully.');
} catch (error) {
if (error.body) {
logger.error(JSON.stringify(error.body));
callback(
null,
`Data processed unsuccessfully: ${JSON.stringify(error.body)}`,
);
} else {
logger.error(error);
callback(null, `Data processed unsuccessfully: ${error}`);
} catch (error) {
logger.error(`Error processing record: ${JSON.stringify(record)}`);
if (error.message) {
logger.error(JSON.stringify(error.message));
} else {
logger.error(error);
}
batchItemFailures.push({ itemIdentifier: record.messageId });
}
}
} else {
logger.info(
'Function not triggered, Missing or not true environment variable present',
);
callback(
null,
'Function not triggered, Missing or not true environment variable present',
);
logger.info('Function not triggered, Missing or not true environment variable present');
}

return { batchItemFailures };
};

export { handler };
78 changes: 56 additions & 22 deletions src/utils/ExtractTestResults.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,67 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */

import { DynamoDBRecord } from 'aws-lambda';
import { DynamoDB } from 'aws-sdk';
import { unmarshall } from '@aws-sdk/util-dynamodb';
import { DateTime } from 'luxon';
import { TestResult } from './TestResult';
import { MCRequest } from './MCRequest';
import { DynamoDBRecord } from 'aws-lambda';
import { TestResultSchema } from '@dvsa/cvs-type-definitions/types/v1/test-result';
import { TestResults } from '@dvsa/cvs-type-definitions/types/v1/enums/testResult.enum'
import { TestStatus } from '@dvsa/cvs-type-definitions/types/v1/enums/testStatus.enum'
import { TestTypeSchema } from '@dvsa/cvs-type-definitions/types/v1/test-type';
import { PROHIBITION_CLEARANCE_TEST } from '@dvsa/cvs-microservice-common/classes/testTypes/Constants';
import { TestTypeHelper } from '@dvsa/cvs-microservice-common/classes/testTypes/testTypeHelper';
import logger from '../observability/Logger';
import { ValidationUtil } from './ValidationUtil';
import { HTTPError } from './HTTPError';
import { PROHIB_CLEARANCE_TEST_TYPE_IDS } from '../assets/Enums';
import { MCRequest } from './MCRequest';
import { ValidationUtil } from './ValidationUtil';

/**
* This is used to extract the relevant fields from the test record that is
* required to be sent to MC in order to clear prohibitions
* @param record
* @param record - a dynamoDB record
* @returns MCRequest[] - an array of MCRequest interface, contains formatted test data
*/
export const extractMCTestResults = (record: DynamoDBRecord): MCRequest[] => {
const testResultUnmarshall = DynamoDB.Converter.unmarshall(record.dynamodb.NewImage);
logger.info(`Processing testResultId: ${JSON.stringify(testResultUnmarshall.testResultId)}`);
let testResultUnmarshall: TestResultSchema;

try {
testResultUnmarshall = unmarshall(record.dynamodb.NewImage as any) as TestResultSchema;
} catch (error) {
throw new Error(`Error unmarshalling test result: ${error}`);
}

logger.info(
`Processing testResultId: ${JSON.stringify(
testResultUnmarshall.testResultId,
)}`,
);
const mcRequest: MCRequest[] = testResultUnmarshall.testTypes
.filter((testType) => PROHIB_CLEARANCE_TEST_TYPE_IDS.IDS.includes(testType.testTypeId))
.filter((testType) => (testType.testResult === ('pass') || testType.testResult === ('prs')))
.filter(() => (testResultUnmarshall.vehicleType === 'hgv') || testResultUnmarshall.vehicleType === 'psv' || testResultUnmarshall.vehicleType === 'trl')
.filter(() => testResultUnmarshall.testStatus === 'submitted')
.map((testResult: TestResult) => ({
vehicleIdentifier: testResultUnmarshall.vehicleType === 'trl' ? testResultUnmarshall.trailerId : testResultUnmarshall.vrm,
testDate: isoDateFormatter(testResult.testTypeEndTimestamp),
.filter((testType) =>
TestTypeHelper.validateTestTypeIdInList(PROHIBITION_CLEARANCE_TEST, testType.testTypeId),
)
.filter(
(testType) =>
testType.testResult === TestResults.PASS || testType.testResult === TestResults.PRS,
)
.filter(
() =>
testResultUnmarshall.vehicleType === 'hgv' ||
testResultUnmarshall.vehicleType === 'psv' ||
testResultUnmarshall.vehicleType === 'trl',
)
.filter(() => testResultUnmarshall.testStatus === TestStatus.SUBMITTED)
.map((testType: TestTypeSchema): MCRequest => ({
vehicleIdentifier: testResultUnmarshall.vehicleType === 'trl'
? testResultUnmarshall.trailerId
: testResultUnmarshall.vrm,
testDate: isoDateFormatter(testType.testTypeEndTimestamp),
vin: testResultUnmarshall.vin,
testResult: calculateTestResult(testResult),
testResult: calculateTestResult(testType.testResult as TestResults),
hgvPsvTrailFlag: testResultUnmarshall.vehicleType.charAt(0).toUpperCase(),
testResultId: testResultUnmarshall.testResultId,
}));
Expand All @@ -47,13 +77,17 @@ export const extractMCTestResults = (record: DynamoDBRecord): MCRequest[] => {
};

/**
* This method is used to change the test result to be a single, uppercase character
* @param testResult
* This method is used to change the test result to be a single, uppercase character.
* @param testResult - enum of string test results PASS/PRS (smc-prohibition doesn't interact with fail).
* @returns string - changes testResult to string of S or R
*/
export const calculateTestResult = (testResult: TestResult): string => (testResult.testResult.toLowerCase() === 'pass' ? 'S' : 'R');
export const calculateTestResult = (testResult: TestResults): string =>
testResult === TestResults.PASS ? 'S' : 'R';

/**
* This method is used to change the format of an iso string to be formatted as yyyy/MM/dd
* @param date
* @param date - string of the test date
* @returns string - date in dd/MM/yyyy format
*/
export const isoDateFormatter = (date: string): string => DateTime.fromISO(date).toFormat('dd/MM/yyyy');
export const isoDateFormatter = (date: string): string =>
DateTime.fromISO(date).toFormat('dd/MM/yyyy');
3 changes: 2 additions & 1 deletion src/utils/HTTPError.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */

/**
Expand All @@ -13,7 +14,7 @@ export class HTTPError extends Error {
* @param statusCode the HTTP status code
* @param body - the response body
*/
constructor( statusCode: number, body: any) {
constructor(statusCode: number, body: any) {
super();
this.statusCode = statusCode;
this.body = body;
Expand Down
7 changes: 0 additions & 7 deletions src/utils/TestResult.ts

This file was deleted.

Loading
Loading