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

fix(integ-test): limit api response to avoid 4k limit #23102

Merged
merged 3 commits into from
Nov 28, 2022
Merged
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
2 changes: 2 additions & 0 deletions packages/@aws-cdk/integ-tests/lib/assertions/api-call-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export interface IApiCall extends IConstruct {
* Assert that the ExpectedResult is equal
* to the result of the AwsApiCall at the given path.
*
* Providing a path will filter the output of the initial API call.
*
* For example the SQS.receiveMessage api response would look
* like:
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,12 @@ export class DeployAssert extends Construct implements IDeployAssert {
Object.defineProperty(this, DEPLOY_ASSERT_SYMBOL, { value: true });
}

public awsApiCall(service: string, api: string, parameters?: any): IApiCall {
public awsApiCall(service: string, api: string, parameters?: any, outputPaths?: string[]): IApiCall {
return new AwsApiCall(this.scope, `AwsApiCall${service}${api}`, {
api,
service,
parameters,
outputPaths,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,28 @@ export class AwsApiCallHandler extends CustomResourceHandler<AwsApiCallRequest,
...flatten(respond),
};

const resp = request.flattenResponse === 'true' ? flatData : respond;
let resp: AwsApiCallResult | { [key: string]: string } = respond;
if (request.outputPaths) {
resp = filterKeys(flatData, request.outputPaths!);
} else if (request.flattenResponse === 'true') {
resp = flatData;
}
console.log(`Returning result ${JSON.stringify(resp)}`);
return resp;
}
}

function filterKeys(object: object, searchStrings: string[]): { [key: string]: string } {
return Object.entries(object).reduce((filteredObject: { [key: string]: string }, [key, value]) => {
for (const searchString of searchStrings) {
if (key.startsWith(`apiCallResponse.${searchString}`)) {
filteredObject[key] = value;
}
}
return filteredObject;
}, {});
}

function isJsonString(value: string): any {
try {
return JSON.parse(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ export interface AwsApiCallRequest {
* @default 'false'
*/
readonly flattenResponse?: string;

/**
* Restrict the data returned by the API call to specific paths in
* the API response. Use this to limit the data returned by the custom
* resource if working with API calls that could potentially result in custom
* response objects exceeding the hard limit of 4096 bytes.
*
* @default - return all data
*/
readonly outputPaths?: string[];
}

/**
Expand Down
14 changes: 14 additions & 0 deletions packages/@aws-cdk/integ-tests/lib/assertions/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ export interface AwsApiCallOptions {
* @default - no parameters
*/
readonly parameters?: any;

/**
* Restrict the data returned by the API call to specific paths in
* the API response. Use this to limit the data returned by the custom
* resource if working with API calls that could potentially result in custom
* response objects exceeding the hard limit of 4096 bytes.
*
* @default - return all data
*/
readonly outputPaths?: string[];
}

/**
Expand Down Expand Up @@ -59,6 +69,7 @@ export class AwsApiCall extends ApiCallBase {
private readonly name: string;

private _assertAtPath?: string;
private _outputPaths?: string[];
private readonly api: string;
private readonly service: string;

Expand All @@ -70,6 +81,7 @@ export class AwsApiCall extends ApiCallBase {
this.name = `${props.service}${props.api}`;
this.api = props.api;
this.service = props.service;
this._outputPaths = props.outputPaths;

this.apiCallResource = new CustomResource(this, 'Default', {
serviceToken: this.provider.serviceToken,
Expand All @@ -81,6 +93,7 @@ export class AwsApiCall extends ApiCallBase {
stateMachineArn: Lazy.string({ produce: () => this.stateMachineArn }),
parameters: this.provider.encode(props.parameters),
flattenResponse: Lazy.string({ produce: () => this.flattenResponse }),
outputPaths: Lazy.list({ produce: () => this._outputPaths }),
salt: Date.now().toString(),
},
resourceType: `${SDK_RESOURCE_TYPE_PREFIX}${this.name}`.substring(0, 60),
Expand All @@ -107,6 +120,7 @@ export class AwsApiCall extends ApiCallBase {

public assertAtPath(path: string, expected: ExpectedResult): IApiCall {
this._assertAtPath = path;
this._outputPaths = [path];
this.expectedResult = expected.result;
this.flattenResponse = 'true';
return this;
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/integ-tests/lib/assertions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface IDeployAssert {
* Messages: [{ Body: 'hello' }],
* }));
*/
awsApiCall(service: string, api: string, parameters?: any): IApiCall;
awsApiCall(service: string, api: string, parameters?: any, outputPaths?: string[]): IApiCall;

/**
* Invoke a lambda function and return the response which can be asserted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,40 @@ describe('SdkHandler', () => {
sinon.assert.calledWith(fake, { DryRun: false });
});
});

test('restrict output path', async () => {
// GIVEN
const responseFake = {
Name: 'bucket-name',
Contents: [
{
Key: 'first-key',
ETag: 'first-key-etag',
},
{
Key: 'second-key',
ETag: 'second-key-etag',
},
],
} as SDK.S3.ListObjectsOutput;
AWS.mock('S3', 'listObjects', sinon.fake.resolves(responseFake));
const handler = sdkHandler() as any;
const request: AwsApiCallRequest = {
service: 'S3',
api: 'listObjects',
parameters: {
Bucket: 'myBucket',
},
outputPaths: ['Name', 'Contents.0.Key'],
};

// WHEN
const response: AwsApiCallResult = await handler.processEvent(request);

// THEN
expect(response).toEqual({
'apiCallResponse.Name': 'bucket-name',
'apiCallResponse.Contents.0.Key': 'first-key',
});
});
});
57 changes: 57 additions & 0 deletions packages/@aws-cdk/integ-tests/test/assertions/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,63 @@ describe('AwsApiCall', () => {

});

test('restrict output paths', () => {
// GIVEN
const app = new App();
const deplossert = new DeployAssert(app);

// WHEN
deplossert.awsApiCall('MyService', 'MyApi', {
param1: 'val1',
param2: 2,
}, ['path1', 'path2']);

// THEN
const template = Template.fromStack(deplossert.scope);
template.resourceCountIs('AWS::Lambda::Function', 1);
template.hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', {
service: 'MyService',
api: 'MyApi',
parameters: {
param1: 'val1',
param2: 2,
},
outputPaths: [
'path1',
'path2',
],
});
});

test('assert at path', () => {
// GIVEN
const app = new App();
const deplossert = new DeployAssert(app);

// WHEN
deplossert.awsApiCall('MyService', 'MyApi', {
param1: 'val1',
param2: 2,
}).assertAtPath('Messages.0.Key', ExpectedResult.exact('first-key'));

// THEN
const template = Template.fromStack(deplossert.scope);
template.resourceCountIs('AWS::Lambda::Function', 1);
template.hasResourceProperties('Custom::DeployAssert@SdkCallMyServiceMyApi', {
service: 'MyService',
api: 'MyApi',
parameters: {
param1: 'val1',
param2: 2,
},
flattenResponse: 'true',
outputPaths: [
'Messages.0.Key',
],
expected: JSON.stringify({ $Exact: 'first-key' }),
});
});

test('add policy to provider', () => {
// GIVEN
const app = new App();
Expand Down