generated from dvsa/dvsa-lambda-starter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ec306fd
commit be3c231
Showing
10 changed files
with
5,574 additions
and
492 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { SecretsManager } from '@dvsa/aws-utilities/classes/secrets-manager-client'; | ||
import { getProfile } from '@dvsa/cvs-feature-flags/profiles/vtx'; | ||
import { EnvironmentVariables } from '@dvsa/cvs-microservice-common/classes/misc/env-vars'; | ||
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; | ||
import { env } from "process"; | ||
import { motRecalls } from "../models/motRecalls"; | ||
import { recallSecret } from "../models/recallSecret"; | ||
import { ERRORS } from "../util/enum"; | ||
import { formatErrorMessage } from "../util/errorMessage"; | ||
import { addHttpHeaders } from "../util/httpHeaders"; | ||
import logger from "../util/logger"; | ||
|
||
const cache: Map<string, Map<string, string>> = new Map(); | ||
|
||
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { | ||
logger.info('Recalls end point called'); | ||
try { | ||
const featureFlags = await getProfile(); | ||
if(!featureFlags.recallsApi){ | ||
logger.error("Recall Feature Flag is undefined") | ||
return addHttpHeaders( { | ||
statusCode: 500, | ||
body: "Recall Feature Flag is undefined" | ||
}); | ||
} | ||
if(!featureFlags.recallsApi.enabled){ | ||
logger.warn('Flag disabled: please enable for recalls functionality'); | ||
return addHttpHeaders( { | ||
statusCode: 200, | ||
body: JSON.stringify({ | ||
manufacturer: null, | ||
hasRecall: false | ||
}) | ||
}); | ||
} | ||
|
||
const vin: string = decodeURIComponent(event.pathParameters?.vin as string); | ||
if (!validateVin(vin)) { | ||
logger.error(formatErrorMessage(ERRORS.VIN_ERROR)); | ||
return addHttpHeaders({ | ||
statusCode: 200, | ||
body: JSON.stringify({ | ||
manufacturer: null, | ||
hasRecall: false | ||
}) | ||
}); | ||
} | ||
|
||
const recalls: motRecalls = await getMotRecallsByVin(vin); | ||
const recallsResponse = filterMotRecalls(recalls); | ||
|
||
return addHttpHeaders({ | ||
statusCode: 200, | ||
body: JSON.stringify(recallsResponse), | ||
}); | ||
|
||
} catch (err : any) { | ||
return addHttpHeaders({ | ||
statusCode: 500, | ||
body: err.message | ||
}) | ||
} | ||
}; | ||
|
||
/** | ||
* Retrieve vehicle recall data from MOT recall API | ||
* @param vin - vin is query parameter | ||
* @returns Promise<motRecalls> - vehicle recall information | ||
*/ | ||
const getMotRecallsByVin = async (vin: string): Promise<motRecalls> => { | ||
// TODO: secrets & auth | ||
const secretResult: recallSecret = await SecretsManager.get( | ||
{ SecretId: EnvironmentVariables.get("MOT_RECALL_SECRET") }, | ||
{}, | ||
{ fromYaml: true } | ||
); | ||
|
||
// check cache, get token if empty, cache it | ||
|
||
|
||
return await fetch(`mot placeholder`, { | ||
headers: { | ||
authorization: "" | ||
} | ||
}) as unknown as motRecalls; | ||
} | ||
|
||
|
||
/** | ||
* Search retrieved recall data for an active recall then construct return object. | ||
* @param vehicleRecalls | ||
* @returns | ||
*/ | ||
const filterMotRecalls = (vehicleRecalls: motRecalls) => { | ||
const time = new Date(); | ||
const recall = vehicleRecalls.recalls.find((recall) => { | ||
if (recall.repairStatus == "NOT_FIXED" && Date.parse(recall.recallCampaignStartDate) < time.getDate()) { | ||
return recall; | ||
} | ||
}); | ||
return { | ||
manufacturer: recall ? vehicleRecalls.manufacturer : null, | ||
hasRecall: !!recall, | ||
} | ||
} | ||
|
||
/** | ||
* validate the input vin has a valid format | ||
* @param vin - query param | ||
* @returns boolean - valid/invalid format | ||
*/ | ||
const validateVin = (vin : any) => { | ||
if (vin !== undefined && vin !== null) { | ||
if (vin.length < 3 | ||
|| vin.length > 21 | ||
|| typeof vin !== 'string' | ||
|| !(/^[0-9a-z]+$/i).test(vin) | ||
|| vin.toUpperCase().includes('O') | ||
|| vin.toUpperCase().includes('I') | ||
|| vin.toUpperCase().includes('Q') | ||
) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; | ||
import { addHttpHeaders } from "../util/httpHeaders"; | ||
import logger from "../util/logger"; | ||
|
||
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { | ||
logger.info('Recalls end point called'); | ||
|
||
const vin: string = decodeURIComponent(event.pathParameters?.vin as string); | ||
const time = new Date(); | ||
|
||
const returnValue = vin + ' - ' + time.toISOString(); | ||
|
||
return addHttpHeaders({ | ||
statusCode: 200, | ||
body: JSON.stringify(returnValue), | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export interface motRecalls { | ||
vin: string, | ||
manufacturer: string, | ||
recalls: recall[], | ||
lastUpdatedDate: string | ||
} | ||
|
||
interface recall { | ||
manufacturerCampaignReference: string, | ||
dvsaCampaignReference: string, | ||
recallCampaignStartDate: string, | ||
repairStatus: repairStatus | ||
} | ||
|
||
type repairStatus = "FIXED" | "NOT_FIXED" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export interface recallSecret { | ||
recall: { | ||
clientID: string; | ||
clientSecret: string; | ||
scopeURL: string; | ||
accessTokenURL: string; | ||
apiKey: string; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import type {APIGatewayProxyResult} from "aws-lambda"; | ||
import { handler } from '../../../src/handler/recalls'; | ||
import {motRecalls} from "../../../src/models/motRecalls"; | ||
import {APIGatewayProxyEvent} from "aws-lambda/trigger/api-gateway-proxy"; | ||
import {ERRORS} from "../../../src/util/enum"; | ||
|
||
describe("Test Recalls endpoint", () => { | ||
beforeEach(() => { | ||
jest.resetAllMocks(); | ||
jest.resetModules(); | ||
}) | ||
describe("handler", () => { | ||
describe("feature flags", () => { | ||
const mockGetProfile = jest.fn(); | ||
jest.mock('@dvsa/cvs-feature-flags/profiles/vtx', () => ({ | ||
getProfile: mockGetProfile, | ||
})); | ||
describe("WHEN the flag is not set", () => { | ||
it("SHOULD return a 500 response", async () => { | ||
mockGetProfile.mockResolvedValue({ | ||
someIncorrectFlag: { | ||
enabled: true, | ||
}, | ||
}); | ||
|
||
const res = await handler({} as APIGatewayProxyEvent); | ||
expect(res.statusCode).toBe(500); | ||
expect(res.body).toBe("Recall Feature Flag is undefined") | ||
}); | ||
}); | ||
describe("WHEN the flag is disabled", () => { | ||
it("SHOULD return a 200 response with flag disabled message", async () =>{ | ||
mockGetProfile.mockResolvedValue({ | ||
recallsApi: { | ||
enabled: false, | ||
}, | ||
}); | ||
|
||
const res = await handler({} as APIGatewayProxyEvent); | ||
expect(res.statusCode).toBe(200); | ||
expect(res.body).toBe("recall API flag is disabled") | ||
}); | ||
}); | ||
describe("WHEN the flag is enabled", () => { | ||
it("SHOULD process normally", async () =>{ | ||
mockGetProfile.mockResolvedValue({ | ||
recallsApi: { | ||
enabled: true, | ||
}, | ||
}); | ||
const res = await handler({} as APIGatewayProxyEvent); | ||
expect(res.statusCode).toBe(200); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe("vin validation", () => { | ||
it('SHOULD return 400 response with a VIN_ERROR when given an INVALID VIN',async () => { | ||
const res = await handler({pathParameters: {vin: "invalid_Vin"}} as unknown as APIGatewayProxyEvent); | ||
|
||
expect(res.statusCode).toBe(400); | ||
expect(res.body).toEqual(ERRORS.VIN_ERROR); | ||
}); | ||
it('SHOULD return 200 response',async () => { | ||
const res = await handler({pathParameters: {vin: "invalid_Vin"}} as unknown as APIGatewayProxyEvent); | ||
|
||
//TODO: happy path VIN validation test | ||
}); | ||
}); | ||
describe("MOT API connection", async () => { | ||
|
||
}); | ||
describe("Constructing return object", () => { | ||
const mockMotRecall : motRecalls = { | ||
vin: "", | ||
manufacturer: "audi", | ||
recalls: | ||
[ | ||
{ | ||
manufacturerCampaignReference: "test", | ||
dvsaCampaignReference: "test", | ||
recallCampaignStartDate: "test", | ||
repairStatus: "NOT_FIXED" | ||
} | ||
], | ||
lastUpdatedDate: (new Date()).toISOString() | ||
} | ||
it("", () => { | ||
|
||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters