Skip to content
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
4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ module.exports = {
testRegex: '\\.test.ts$',
coverageThreshold: {
global: {
branches: 80,
statements: 90,
branches: 70,
statements: 80,
},
},
coverageDirectory: 'coverage/ts',
Expand Down
35 changes: 27 additions & 8 deletions src/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
HandlerErrorCode,
OperationStatus,
Optional,
RequestContext,
} from './interface';
import { ProviderLogHandler } from './log-delivery';
import { MetricsPublisherProxy } from './metrics';
Expand Down Expand Up @@ -140,6 +139,7 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {
let request: BaseResourceHandlerRequest<T>;
let action: Action;
let event: TestEvent;
let callbackContext: Map<string, any>;
try {
event = new TestEvent(eventData);
const creds = event.credentials as Credentials;
Expand All @@ -162,17 +162,25 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {

session = SessionProxy.getSession(creds, event.region);
action = event.action;

if (!event.callbackContext) {
callbackContext = new Map<string, any>();
} else if (
event.callbackContext instanceof Array ||
event.callbackContext instanceof Map
) {
callbackContext = new Map<string, any>(event.callbackContext);
} else {
callbackContext = new Map<string, any>(
Object.entries(event.callbackContext)
);
}
} catch (err) {
LOGGER.error('Invalid request');
throw new InternalFailure(`${err} (${err.name})`);
}

return [
session,
request,
action,
event.callbackContext || new Map<string, any>(),
];
return [session, request, action, callbackContext];
};

// @ts-ignore
Expand Down Expand Up @@ -242,7 +250,18 @@ export abstract class BaseResource<T extends BaseModel = BaseModel> {
event.requestData.providerCredentials
);
action = event.action;
callbackContext = event.callbackContext || new Map<string, any>();
if (!event.callbackContext) {
callbackContext = new Map<string, any>();
} else if (
event.callbackContext instanceof Array ||
event.callbackContext instanceof Map
) {
callbackContext = new Map<string, any>(event.callbackContext);
} else {
callbackContext = new Map<string, any>(
Object.entries(event.callbackContext)
);
}
} catch (err) {
LOGGER.error('Invalid request');
throw new InvalidRequest(`${err} (${err.name})`);
Expand Down
3 changes: 2 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,5 +188,6 @@ Map.prototype.toObject = function (): any {
* Defines the default JSON representation of a Map to be an array of key-value pairs.
*/
Map.prototype.toJSON = function <K, V>(this: Map<K, V>): Array<[K, V]> {
return Array.from(this.entries());
// @ts-ignore
return Object.fromEntries(this);
};
118 changes: 100 additions & 18 deletions tests/lib/resource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ import {
CfnResponse,
HandlerErrorCode,
OperationStatus,
RequestContext,
} from '../../src/interface';
import { ProviderLogHandler } from '../../src/log-delivery';
import { MetricsPublisherProxy } from '../../src/metrics';
import { handlerEvent, HandlerSignatures, BaseResource } from '../../src/resource';
import { HandlerRequest, LambdaContext } from '../../src/utils';
import { HandlerRequest } from '../../src/utils';

const mockResult = (output: any): jest.Mock => {
return jest.fn().mockReturnValue({
Expand Down Expand Up @@ -195,23 +194,34 @@ describe('when getting resource', () => {
await resource.entrypoint(entrypointPayload, null);
});

test('entrypoint with context', async () => {
entrypointPayload['requestContext'] = { a: 'b' };
test('entrypoint with callback context', async () => {
entrypointPayload['callbackContext'] = { a: 'b' };
const event: ProgressEvent = ProgressEvent.success(null, { c: 'd' });
const mockHandler: jest.Mock = jest.fn(() => event);
const resource = new Resource(TYPE_NAME, MockModel);
resource.addHandler(Action.Create, mockHandler);
await resource.entrypoint(entrypointPayload, null);
const response: CfnResponse<Resource> = await resource.entrypoint(
entrypointPayload,
null
);
expect(response).toMatchObject({
message: '',
status: OperationStatus.Success,
callbackDelaySeconds: 0,
});
expect(mockHandler).toBeCalledTimes(1);
expect(mockHandler).toBeCalledWith(
expect.any(SessionProxy),
expect.any(BaseResourceHandlerRequest),
new Map(Object.entries(entrypointPayload['callbackContext']))
);
});

test('entrypoint without context', async () => {
entrypointPayload['requestContext'] = null;
test('entrypoint without callback context', async () => {
entrypointPayload['callbackContext'] = null;
const mockLogDelivery: jest.Mock = (ProviderLogHandler.setup as unknown) as jest.Mock;
const event: ProgressEvent = ProgressEvent.success(
new Map(Object.entries({ a: 'b' })),
{ c: 'd' }
);
const event: ProgressEvent = ProgressEvent.progress(null, { c: 'd' });
event.callbackDelaySeconds = 5;
const mockHandler: jest.Mock = jest.fn(() => event);
const resource = new Resource(TYPE_NAME, MockModel);
resource.addHandler(Action.Create, mockHandler);
Expand All @@ -222,10 +232,16 @@ describe('when getting resource', () => {
expect(mockLogDelivery).toBeCalledTimes(1);
expect(response).toMatchObject({
message: '',
status: OperationStatus.Success,
callbackDelaySeconds: 0,
status: OperationStatus.InProgress,
callbackDelaySeconds: 5,
callbackContext: { c: 'd' },
});
expect(mockHandler).toBeCalledTimes(1);
expect(mockHandler).toBeCalledWith(
expect.any(SessionProxy),
expect.any(BaseResourceHandlerRequest),
new Map()
);
});

test('entrypoint success without caller provider creds', async () => {
Expand Down Expand Up @@ -261,6 +277,36 @@ describe('when getting resource', () => {
expect(parseRequest).toThrow(/missing.+awsAccountId/i);
});

test('parse request with object literal callback context', () => {
const callbackContext = new Map();
callbackContext.set('a', 'b');
entrypointPayload['callbackContext'] = { a: 'b' };
const payload = new Map(Object.entries(entrypointPayload));
const resource = getResource();
const [sessions, action, callback, request] = resource.constructor[
'parseRequest'
](payload);
expect(sessions).toBeDefined();
expect(action).toBeDefined();
expect(callback).toMatchObject(callbackContext);
expect(request).toBeDefined();
});

test('parse request with map callback context', () => {
const callbackContext = new Map();
callbackContext.set('a', 'b');
entrypointPayload['callbackContext'] = callbackContext;
const payload = new Map(Object.entries(entrypointPayload));
const resource = getResource();
const [sessions, action, callback, request] = resource.constructor[
'parseRequest'
](payload);
expect(sessions).toBeDefined();
expect(action).toBeDefined();
expect(callback).toMatchObject(callbackContext);
expect(request).toBeDefined();
});

test('cast resource request invalid request', () => {
const payload = new Map(Object.entries(entrypointPayload));
const request = HandlerRequest.deserialize(payload);
Expand Down Expand Up @@ -342,15 +388,15 @@ describe('when getting resource', () => {
test('add handler', () => {
class ResourceEventHandler extends BaseResource {
@handlerEvent(Action.Create)
public create() {}
public create(): void {}
@handlerEvent(Action.Read)
public read() {}
public read(): void {}
@handlerEvent(Action.Update)
public update() {}
public update(): void {}
@handlerEvent(Action.Delete)
public delete() {}
public delete(): void {}
@handlerEvent(Action.List)
public list() {}
public list(): void {}
}
const handlers: HandlerSignatures = new HandlerSignatures();
const resource = new ResourceEventHandler(null, null, handlers);
Expand Down Expand Up @@ -445,6 +491,42 @@ describe('when getting resource', () => {
expect(parseTestRequest).toThrow(/missing.+credentials/i);
});

test('parse test request with object literal callback context', () => {
const callbackContext = new Map();
callbackContext.set('a', 'b');
testEntrypointPayload['callbackContext'] = { a: 'b' };
class Model extends BaseModel {
['constructor']: typeof Model;
}
const resource = new Resource(TYPE_NAME, Model);
const payload = new Map(Object.entries(testEntrypointPayload));
const [session, request, action, callback] = resource['parseTestRequest'](
payload
);
expect(session).toBeDefined();
expect(action).toBeDefined();
expect(callback).toMatchObject(callbackContext);
expect(request).toBeDefined();
});

test('parse test request with map callback context', () => {
const callbackContext = new Map();
callbackContext.set('a', 'b');
testEntrypointPayload['callbackContext'] = callbackContext;
class Model extends BaseModel {
['constructor']: typeof Model;
}
const resource = new Resource(TYPE_NAME, Model);
const payload = new Map(Object.entries(testEntrypointPayload));
const [session, request, action, callback] = resource['parseTestRequest'](
payload
);
expect(session).toBeDefined();
expect(action).toBeDefined();
expect(callback).toMatchObject(callbackContext);
expect(request).toBeDefined();
});

test('parse test request valid request', () => {
const mockDeserialize: jest.Mock = jest
.fn()
Expand Down