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.
Merge pull request #14 from dvsa/feature/CB2-10573
feat(CB2-10573): Implement AWS AppConfig Feature Flagging
- Loading branch information
Showing
15 changed files
with
16,194 additions
and
7,340 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
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,5 @@ | ||
version: v1.5.0 | ||
ignore: | ||
'SNYK-JS-RAILROADDIAGRAMS-6282875': | ||
- '* > railroad-diagrams': | ||
reason: 'No fix available - file with vulnerability not used' |
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,43 @@ | ||
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; | ||
import { FeatureFlagsClientName } from '@dvsa/cvs-microservice-common/feature-flags'; | ||
import 'dotenv/config'; | ||
import logger from '../util/logger'; | ||
import { Clients } from './util/clients'; | ||
import { headers } from '../util/headers'; | ||
|
||
const parseClientFromEvent = (value = ''): FeatureFlagsClientName => { | ||
const clientValue = value.toUpperCase(); | ||
return FeatureFlagsClientName[clientValue as keyof typeof FeatureFlagsClientName]; | ||
}; | ||
|
||
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { | ||
logger.info('feature flag endpoint called'); | ||
|
||
const client = parseClientFromEvent(event?.pathParameters?.client); | ||
if (client === undefined) { | ||
return { | ||
statusCode: 404, | ||
body: JSON.stringify('Client not found'), | ||
headers, | ||
}; | ||
} | ||
|
||
try { | ||
const config = Clients.get(client); | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const flags = await config!(); | ||
|
||
return { | ||
statusCode: 200, | ||
body: JSON.stringify(flags), | ||
headers, | ||
}; | ||
} catch (error) { | ||
logger.error(error); | ||
return { | ||
statusCode: 500, | ||
body: JSON.stringify('Error fetching feature flags'), | ||
headers, | ||
}; | ||
} | ||
}; |
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,26 @@ | ||
import { getVTXProfile, getVTAProfile, getVTMProfile } from '@dvsa/cvs-microservice-common/feature-flags/profiles'; | ||
import { FeatureFlagsClientName } from '@dvsa/cvs-microservice-common/feature-flags'; | ||
|
||
/* | ||
* This mapping exists as a way to cache the client profiles outside the handler and in the context of the | ||
* execution environment (lambda). This means these will only be hydrated for cold starts. | ||
* | ||
* We want to do this for the get handler because this endopint will be getting called frequently. In this | ||
* scenario it means cold starts should be less frequent, so we can rely on the app config caching at the container. | ||
*/ | ||
export const Clients = new Map( | ||
[ | ||
[ | ||
FeatureFlagsClientName.VTX, | ||
() => getVTXProfile(), | ||
], | ||
[ | ||
FeatureFlagsClientName.VTA, | ||
() => getVTAProfile(), | ||
], | ||
[ | ||
FeatureFlagsClientName.VTM, | ||
() => getVTMProfile(), | ||
], | ||
], | ||
); |
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,5 @@ | ||
export const headers = { | ||
'Access-Control-Allow-Origin': '*', | ||
'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', | ||
'Access-Control-Allow-Methods': 'GET,OPTIONS', | ||
}; |
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,64 @@ | ||
/* eslint-disable import/first */ | ||
const mockGetVTXProfile = jest.fn(); | ||
|
||
import { APIGatewayProxyEvent } from 'aws-lambda'; | ||
|
||
import { handler } from '../../src/feature-flags/get'; | ||
import { headers } from '../../src/util/headers'; | ||
|
||
jest.mock('@dvsa/cvs-microservice-common/feature-flags/profiles', () => ({ | ||
getVTXProfile: mockGetVTXProfile, | ||
})); | ||
|
||
describe('feature flags endpoint', () => { | ||
const validEvent: Partial<APIGatewayProxyEvent> = { | ||
pathParameters: { | ||
client: 'vtx', | ||
}, | ||
body: '', | ||
}; | ||
|
||
type CvsFeatureFlags = { | ||
firstFlag: { | ||
enabled: boolean | ||
}, | ||
}; | ||
|
||
const featureFlags = { | ||
firstFlag: { | ||
enabled: true, | ||
}, | ||
}; | ||
|
||
it('should return 404 when an invalid client is specified', async () => { | ||
const invalidEvent: Partial<APIGatewayProxyEvent> = { | ||
pathParameters: { | ||
client: 'invalid client', | ||
}, | ||
body: '', | ||
}; | ||
const result = await handler(invalidEvent as APIGatewayProxyEvent); | ||
|
||
expect(result).toEqual({ statusCode: 404, body: '"Client not found"', headers }); | ||
}); | ||
|
||
it('should return 500 when app config throws an error', async () => { | ||
mockGetVTXProfile.mockRejectedValue('Error!'); | ||
|
||
const result = await handler(validEvent as APIGatewayProxyEvent); | ||
|
||
expect(mockGetVTXProfile).toHaveBeenCalled(); | ||
expect(result).toEqual({ statusCode: 500, body: '"Error fetching feature flags"', headers }); | ||
}); | ||
|
||
it('should return feature flags successfully', async () => { | ||
mockGetVTXProfile.mockReturnValue(Promise.resolve(featureFlags)); | ||
|
||
const result = await handler(validEvent as APIGatewayProxyEvent); | ||
const flags = JSON.parse(result.body) as CvsFeatureFlags; | ||
|
||
expect(mockGetVTXProfile).toHaveBeenCalled(); | ||
expect(result.statusCode).toBe(200); | ||
expect(flags).toEqual(featureFlags); | ||
}); | ||
}); |
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,80 @@ | ||
/* eslint-disable import/first */ | ||
const mockGetVTXProfile = jest.fn(); | ||
const mockGetVTAProfile = jest.fn(); | ||
const mockGetVTMProfile = jest.fn(); | ||
|
||
import { FeatureFlagsClientName } from '@dvsa/cvs-microservice-common/feature-flags'; | ||
import { Clients } from '../../../src/feature-flags/util/clients'; | ||
|
||
jest.mock('@dvsa/cvs-microservice-common/feature-flags/profiles', () => ({ | ||
getVTXProfile: mockGetVTXProfile, | ||
getVTAProfile: mockGetVTAProfile, | ||
getVTMProfile: mockGetVTMProfile, | ||
})); | ||
|
||
describe('feature flag clients', () => { | ||
type CvsFeatureFlags = { | ||
firstFlag: Flag, | ||
secondFlag: Flag, | ||
}; | ||
|
||
type Flag = { | ||
enabled: boolean | ||
}; | ||
|
||
const validFlags = { | ||
firstFlag: { | ||
enabled: true, | ||
}, | ||
secondFlag: { | ||
enabled: false, | ||
}, | ||
}; | ||
|
||
beforeEach(() => { | ||
mockGetVTXProfile.mockReset(); | ||
mockGetVTAProfile.mockReset(); | ||
mockGetVTMProfile.mockReset(); | ||
}); | ||
|
||
test.each` | ||
clientName | mock | ||
${FeatureFlagsClientName.VTX} | ${mockGetVTXProfile} | ||
${FeatureFlagsClientName.VTA} | ${mockGetVTAProfile} | ||
${FeatureFlagsClientName.VTM} | ${mockGetVTMProfile} | ||
`( | ||
'should not fetch feature flags when just loading', | ||
({ clientName, mock }: { clientName: FeatureFlagsClientName, mock: jest.Mock }) => { | ||
mock.mockReturnValue(Promise.resolve(validFlags)); | ||
|
||
const profile = Clients.get(clientName); | ||
|
||
expect(profile).not.toBeNull(); | ||
expect(mock).toHaveBeenCalledTimes(0); | ||
}, | ||
); | ||
|
||
test.each` | ||
clientName | mock | ||
${FeatureFlagsClientName.VTX} | ${mockGetVTXProfile} | ||
${FeatureFlagsClientName.VTA} | ${mockGetVTAProfile} | ||
${FeatureFlagsClientName.VTM} | ${mockGetVTMProfile} | ||
`( | ||
'should fetch feature flags each time theyre invoked', | ||
async ({ clientName, mock }: { clientName: FeatureFlagsClientName, mock: jest.Mock }) => { | ||
mock.mockReturnValue(Promise.resolve(validFlags)); | ||
|
||
const firstAppConfig = Clients.get(clientName); | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const firstFlags = await firstAppConfig!() as CvsFeatureFlags; | ||
|
||
const secondAppConfig = Clients.get(clientName); | ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | ||
const secondFlags = await secondAppConfig!() as CvsFeatureFlags; | ||
|
||
expect(firstFlags).toEqual(validFlags); | ||
expect(secondFlags).toEqual(validFlags); | ||
expect(mock).toHaveBeenCalledTimes(2); | ||
}, | ||
); | ||
}); |
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 |
---|---|---|
@@ -1,12 +1,13 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es2019", | ||
"module": "commonjs", | ||
"module": "Node16", | ||
"allowJs": true, | ||
"checkJs": true, | ||
"sourceMap": true, | ||
"esModuleInterop": true, | ||
"strict": true | ||
"strict": true, | ||
"moduleResolution": "Node16" | ||
}, | ||
"include": ["src/**/*.ts", "tests/**/*.ts"] | ||
} |
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