-
Notifications
You must be signed in to change notification settings - Fork 8
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
feat: improve fake api gateway request events creation #91
base: master
Are you sure you want to change the base?
feat: improve fake api gateway request events creation #91
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MatheusBaldi Thanks for submitting this! On first pass, a few observations:
- This is a fairly large MR that is fundamentally changing how the test events are generated in this project. Instead of changing everything, would it be possible to just change the API Gateway test event in this initial PR and then submit a follow up PR for the ALB events? This way the initial pattern can be set while keeping the set of changes small.
- By convention we lean towards the OR operator (
||
) over the null coalescing operator (??
). Eventually we want to reconsider that stance, but for now can we keep with||
to maintain consistency. - This PR is adding a bunch of types which basically mirror the AWS provided types. The goal of these "generator" functions is to make developer's lives better. Rather than taking a 1-to-1 input from the event, can we abstract the internal workings of the events some? For example:
// in tests/samples.ts...
interface MakeAPIGatewayRequestParams {
// Only defining the overrides that are needed
httpMethod?: string;
headers?: StringMap;
}
function makeAPIGatewayRequest(params: MakeAPIGatewayRequestParams): APIGatewayRequestEvent {
return {
// ... omitted for brevity ...
httpMethod: params.httpMethod || 'GET',
requestContext: makeAPIGatewayRequestContext({
httpMethod: params.httpMethod,
}),
headers: {
// ...
...(params.headers || {})
},
multiValueHeaders: {
// ...
...Object.fromEntries(Object.entries(params.headers || {}).map(([ key, value ]), () => {
return [ key, [ value ] ];
}))
},
// ...
};
}
interface MakeAPIGatewayRequestContextParams {
httpMethod?: string;
}
function makeAPIGatewayRequestContext(params: MakeAPIGatewayRequestContextParams): APIGatewayEventRequestContext {
return {
// ...
httpMethod: params.httpMethod || 'GET',
// ...
};
};
@onebytegone Thank you for your observations.
function makeAPIGatewayRequest(params?: MakeAPIGatewayRequestParams): APIGatewayRequestEvent {
const defaultHttpMethod = params.httpMethod || 'GET';
const defaultHeader = makeAPIGatewayRequestHeader(params.headers);
return {
// ... omitted for brevity ...
httpMethod: defaultHttpMethod,
// if requestContext not specified, define a default as we want, otherwise, use the input
requestContext: params?.requestContext === undefined
? makeAPIGatewayRequestContext({ // this function will know what other fields to populate by default
httpMethod: defaultHttpMethod,
})
: params.requestContext,
headers: params?.headers === undefined
? defaultHeader
: params.headers,
multiValueHeaders: params?.multiValueHeaders === undefined
? makeAPIGatewayRequestMultivalueHeader(defaultHeader) //
: params.multiValueHeaders,
// ...
};
} Also, we could have other helper functions that builds parts of the object before passing them to the generator, in that way, the responsibility of being specific goes to where the generator function is called. Now two more questions
|
1 Feel free to update this PR interface MakeAPIGatewayRequestEventParams {
httpMethod?: string | null;
}
function makeAPIGatewayRequestEvent(params?: MakeAPIGatewayRequestEventParams): APIGatewayRequestEvent {
const httpMethod = withDefault(params?.httpMethod, 'GET');
return {
// ... omitted for brevity ...
httpMethod,
requestContext: makeAPIGatewayRequestContext({
httpMethod,
}),
};
}
function withDefault<T>(value: T, defaultValue: T): T | undefined {
if (isUndefined(value)) {
return defaultValue;
}
if (value === null) {
return undefined;
}
return value;
}
|
receives a value in its first parameter `value` and a default value in its second parameter `defaultValue`. If `value` is undefined, returns `defaultValue`, otherwise returns `value`
…Event on integration-tests - makeAPIGatewayRequestEvent accepts an input object, allowing the developer to change values of desired fields when needed, while apiGatewayRequest would always return the same object
…Event on Request tests - makeAPIGatewayRequestEvent accepts an input object, allowing the developer to change values of desired fields when needed, while apiGatewayRequest would always return the same object
…Event on Response tests - makeAPIGatewayRequestEvent accepts an input object, allowing the developer to change values of desired fields when needed, while apiGatewayRequest would always return the same object
1afe308
to
23bd78e
Compare
…uestEvent where _.extends was applied
23bd78e
to
06ef2c0
Compare
export const makeAPIGatewayRequestEvent = (params?: MakeAPIGatewayRequestEventParams): APIGatewayRequestEvent => { | ||
const httpMethod = withDefault(params?.httpMethod, 'GET'), | ||
path = withDefault(params?.path, '/echo/asdf/a'), | ||
headers = withDefault(params?.headers, makeAPIGatewayRequestEventHeaders()); | ||
|
||
const multiValueHeaders = withDefault( | ||
params?.multiValueHeaders, | ||
makeAPIGatewayRequestEventMultiValueHeader() | ||
); | ||
|
||
return { | ||
path, | ||
httpMethod, | ||
body: null, | ||
isBase64Encoded: false, | ||
resource: '/{proxy+}', | ||
pathParameters: { proxy: path }, | ||
stageVariables: null, | ||
requestContext: makeAPIGatewayRequestContext({ | ||
httpMethod, | ||
}), | ||
headers, | ||
multiValueHeaders, | ||
queryStringParameters: { | ||
'foo[a]': 'bar b', | ||
x: '2', | ||
y: 'z', | ||
}, | ||
multiValueQueryStringParameters: { | ||
'foo[a]': [ 'bar b', 'baz c' ], | ||
x: [ '1', '2' ], | ||
y: [ 'z' ], | ||
}, | ||
}; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've also thought of another approach for handling default values by using the object behavior of making keys that comes at last overwrite the preceding ones:
// say params is:
const params = {
path: '/another/path/a',
httpMethod: 'POST',
};
export const makeAPIGatewayRequestEvent = (params?: MakeAPIGatewayRequestEventParams): APIGatewayRequestEvent => {
// still treating these values because in the default case they must be reflected in other
// parts of the object
const httpMethod = withDefault(params?.httpMethod, 'GET'),
path = withDefault(params?.path, '/echo/asdf/a');
return {
path: '/echo/asdf/a',
httpMethod,
body: null,
isBase64Encoded: false,
resource: '/{proxy+}',
pathParameters: { proxy: path },
stageVariables: null,
requestContext: makeAPIGatewayRequestContext({
httpMethod,
}),
headers: makeAPIGatewayRequestEventHeaders(),
multiValueHeaders: makeAPIGatewayRequestEventMultiValueHeader(),
queryStringParameters: {
'foo[a]': 'bar b',
x: '2',
y: 'z',
},
multiValueQueryStringParameters: {
'foo[a]': [ 'bar b', 'baz c' ],
x: [ '1', '2' ],
y: [ 'z' ],
},
...params,
};
};
It seems to have a similar behavior and it is easier for developers to add more of the dynamic fields in the future, since they only need to update the interface if no further treatment is needed like in the cases of path
and httpMethod
. What do you think?
@onebytegone the amount of changed files increased because the refactorings demanded some other files that used the old |
No description provided.