Skip to content

Commit

Permalink
test(amplify-appsync-simulator): add resolver tests
Browse files Browse the repository at this point in the history
Added tests for unit and pipeline resolvers
  • Loading branch information
yuth committed May 13, 2020
1 parent 56fdd00 commit da712b8
Show file tree
Hide file tree
Showing 4 changed files with 385 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
AppSyncMockFile,
AppSyncSimulatorBaseResolverConfig,
RESOLVER_KIND,
AppSyncSimulatorUnitResolverConfig,
} from '../type-definition';

jest.mock('../schema');
Expand Down Expand Up @@ -48,9 +49,10 @@ describe('AmplifyAppSyncSimulator', () => {
});

it('should retain the original configuration when config has error', () => {
const resolver: AppSyncSimulatorBaseResolverConfig = {
const resolver: AppSyncSimulatorUnitResolverConfig = {
fieldName: 'echo',
typeName: 'Query',
dataSourceName: 'echoFn',
kind: RESOLVER_KIND.UNIT,
requestMappingTemplateLocation: 'missing/Resolver.req.vtl',
responseMappingTemplateLocation: 'missing/Resolver.resp.vtl',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import { AppSyncPipelineResolver } from '../../resolvers/pipeline-resolver';
import { AmplifyAppSyncSimulator } from '../..';
import { RESOLVER_KIND, AppSyncSimulatorPipelineResolverConfig } from '../../type-definition';
describe('Pipeline Resolvers', () => {
const getFunction = jest.fn();
const getMappingTemplate = jest.fn();
const simulatorContext: AmplifyAppSyncSimulator = ({
getFunction,
getMappingTemplate,
} as any) as AmplifyAppSyncSimulator;
let baseConfig;
beforeEach(() => {
jest.resetAllMocks();
getFunction.mockReturnValue({ resolve: () => 'foo' });
getMappingTemplate.mockReturnValue('TEMPLATE');
baseConfig = {
fieldName: 'fn1',
typeName: 'Query',
kind: RESOLVER_KIND.PIPELINE,
functions: ['fn1', 'fn2'],
};
});
it('should initialize when the request and response mapping templates are inline templates', () => {
const config: AppSyncSimulatorPipelineResolverConfig = {
...baseConfig,
requestMappingTemplate: 'request',
responseMappingTemplate: 'response',
};
expect(() => new AppSyncPipelineResolver(config, simulatorContext)).not.toThrow();
expect(getMappingTemplate).not.toHaveBeenCalled();
});

it('should work when the request and response mapping are external template', () => {
const config: AppSyncSimulatorPipelineResolverConfig = {
...baseConfig,
requestMappingTemplateLocation: 'resolvers/request',
responseMappingTemplateLocation: 'resolvers/response',
};
expect(() => new AppSyncPipelineResolver(config, simulatorContext)).not.toThrow();
expect(getMappingTemplate).toHaveBeenCalledTimes(2);
});

it('should throw error when request templates are missing', () => {
getMappingTemplate.mockImplementation(() => {
throw new Error('Missing template');
});
expect(() => new AppSyncPipelineResolver(baseConfig, simulatorContext)).toThrowError('Missing request mapping template');
expect(getMappingTemplate).toHaveBeenCalled();
});

describe('resolve', () => {
let resolver: AppSyncPipelineResolver;
const baseConfig: AppSyncSimulatorPipelineResolverConfig = {
fieldName: 'fn1',
typeName: 'Query',
kind: RESOLVER_KIND.PIPELINE,
functions: ['fn1', 'fn2'],
requestMappingTemplateLocation: 'request',
responseMappingTemplateLocation: 'response',
};
let templates;
let fnImpl;
beforeEach(() => {
fnImpl = {
fn1: {
resolve: jest.fn().mockImplementation((source, args, stash, prevResult, context, info) => {
return {
result: 'FN1-RESULT',
stash: { ...stash, exeSeq: [...(stash.exeSeq || []), 'fn1'] },
};
}),
},
fn2: {
resolve: jest.fn().mockImplementation((source, args, stash, prevResult, context, info) => {
return {
result: 'FN2-RESULT',
stash: { ...stash, exeSeq: [...(stash.exeSeq || []), 'fn2'] },
};
}),
},
};
getFunction.mockImplementation(fnName => fnImpl[fnName]);
templates = {
request: {
render: jest.fn().mockImplementation(({ stash }) => ({
result: 'REQUEST_TEMPLATE_RESULT',
errors: [],
stash: { ...stash, exeSeq: [...(stash.exeSeq || []), 'REQUEST-MAPPING-TEMPLATE'] },
})),
},
response: {
render: jest.fn().mockImplementation(({ stash }) => ({
result: 'RESPONSE_TEMPLATE_RESULT',
errors: [],
stash: { ...stash, exeSeq: [...stash.exeSeq, 'fn2'] },
})),
},
};

getMappingTemplate.mockImplementation(templateName => templates[templateName]);
resolver = new AppSyncPipelineResolver(baseConfig, simulatorContext);
});

it('should render requestMapping template', async () => {
const source = 'SOURCE';
const args = { arg1: 'val' };
const context = {
appsyncErrors: [],
};
const info = {};
const result = await resolver.resolve(source, args, context, info);
expect(result).toEqual('RESPONSE_TEMPLATE_RESULT');
expect(templates['request'].render).toHaveBeenCalledWith(
{
source,
arguments: args,
stash: {},
},
context,
info,
);

expect(getMappingTemplate).toHaveBeenCalledTimes(4); // 2 times in constructor and 2 times for resolving
expect(getFunction).toHaveBeenCalled();
});

it('should pass stash and prevResult between functions and templates', async () => {
const source = 'SOURCE';
const args = { arg1: 'val' };
const context = {
appsyncErrors: [],
};
const info = {};
const result = await resolver.resolve(source, args, context, info);
expect(result).toEqual('RESPONSE_TEMPLATE_RESULT');
expect(fnImpl.fn1.resolve).toHaveBeenLastCalledWith(
source,
args,
{ exeSeq: ['REQUEST-MAPPING-TEMPLATE'] },
'REQUEST_TEMPLATE_RESULT',
context,
info,
);

expect(fnImpl.fn2.resolve).toHaveBeenLastCalledWith(
source,
args,
{ exeSeq: ['REQUEST-MAPPING-TEMPLATE', 'fn1'] },
'FN1-RESULT',
context,
info,
);

expect(templates['response'].render).toHaveBeenCalledWith(
{
source,
arguments: args,
prevResult: 'FN2-RESULT',
stash: { exeSeq: ['REQUEST-MAPPING-TEMPLATE', 'fn1', 'fn2'] },
},
context,
info,
);
});

it('should not call response mapping template when #return is called', async () => {
templates.request.render.mockReturnValue({ isReturn: true, result: 'REQUEST_TEMPLATE_RESULT', templateErrors: [] });
const source = 'SOURCE';
const args = { arg1: 'val' };
const context = {
appsyncErrors: [],
};
const info = {};
const result = await resolver.resolve(source, args, context, info);
expect(result).toEqual('REQUEST_TEMPLATE_RESULT');
});

it('should merge template errors', async () => {
templates.request.render.mockReturnValue({
isReturn: false,
stash: {},
result: 'REQUEST_TEMPLATE_RESULT',
errors: ['REQUEST_TEMPLATE_ERROR'],
});
templates.response.render.mockReturnValue({
isReturn: false,
result: 'RESPONSE_TEMPLATE_RESULT',
errors: ['RESPONSE_TEMPLATE_ERROR'],
});

const source = 'SOURCE';
const args = { arg1: 'val' };
const context = {
appsyncErrors: [],
};
const info = {};
const result = await resolver.resolve(source, args, context, info);
expect(result).toEqual('RESPONSE_TEMPLATE_RESULT');
expect(context.appsyncErrors).toEqual(['REQUEST_TEMPLATE_ERROR', 'RESPONSE_TEMPLATE_ERROR']);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { AppSyncUnitResolver } from '../../resolvers/unit-resolver';
import { AmplifyAppSyncSimulator } from '../..';
import { RESOLVER_KIND, AppSyncSimulatorUnitResolverConfig } from '../../type-definition';

describe('Unit resolver', () => {
const getDataLoader = jest.fn();
const getMappingTemplate = jest.fn();
const simulatorContext: AmplifyAppSyncSimulator = ({
getDataLoader,
getMappingTemplate,
} as any) as AmplifyAppSyncSimulator;
let baseConfig;

beforeEach(() => {
jest.resetAllMocks();
getDataLoader.mockReturnValue({
load: () => {
return 'DATA';
},
});
getMappingTemplate.mockReturnValue('TEMPLATE');
baseConfig = {
fieldName: 'getPost',
typeName: 'Query',
kind: RESOLVER_KIND.UNIT,
dataSourceName: 'TodoTable',
};
});

it('should initialize when the request and response mapping templates are inline templates', () => {
const config: AppSyncSimulatorUnitResolverConfig = {
...baseConfig,
requestMappingTemplate: 'request',
responseMappingTemplate: 'response',
};
expect(() => new AppSyncUnitResolver(config, simulatorContext)).not.toThrow();
expect(getMappingTemplate).not.toHaveBeenCalled();
});

it('should work when the request and response mapping are external template', () => {
const config: AppSyncSimulatorUnitResolverConfig = {
...baseConfig,
requestMappingTemplateLocation: 'resolvers/request',
responseMappingTemplateLocation: 'resolvers/response',
};
expect(() => new AppSyncUnitResolver(config, simulatorContext)).not.toThrow();
expect(getMappingTemplate).toHaveBeenCalledTimes(2);
});

it('should throw error when request templates are missing', () => {
getMappingTemplate.mockImplementation(() => {
throw new Error('Missing template');
});
expect(() => new AppSyncUnitResolver(baseConfig, simulatorContext)).toThrowError('Missing request mapping template');
expect(getMappingTemplate).toHaveBeenCalled();
});

describe('resolve', () => {
let templates;
let resolver;

const info = {};
const DATA_FROM_DATA_SOURCE = 'DATA FROM DATA SOURCE';
const REQUEST_TEMPLATE_RESULT = {
version: '2017-02-29',
result: 'REQUEST_TEMPLATE_RESULT',
};
const RESPONSE_TEMPLATE_RESULT = {
version: '2017-02-29',
data: 'RESPONSE_TEMPLATE_RESULT',
};
let dataFetcher;

const source = 'SOURCE';
const args = { key: 'value' };
const context = {
appsyncErrors: [],
};

beforeEach(() => {
context.appsyncErrors = [];
templates = {
request: {
render: jest.fn().mockImplementation(() => ({
result: REQUEST_TEMPLATE_RESULT,
errors: [],
})),
},
response: {
render: jest.fn().mockImplementation(() => ({
result: RESPONSE_TEMPLATE_RESULT,
errors: [],
})),
},
};

dataFetcher = jest.fn().mockResolvedValue(DATA_FROM_DATA_SOURCE);

getDataLoader.mockReturnValue({
load: dataFetcher,
});
getMappingTemplate.mockImplementation(templateName => {
return templates[templateName];
});

resolver = new AppSyncUnitResolver(
{ ...baseConfig, requestMappingTemplateLocation: 'request', responseMappingTemplateLocation: 'response' },
simulatorContext,
);
});

it('should resolve', async () => {
const result = await resolver.resolve(source, args, context, info);
expect(result).toEqual(RESPONSE_TEMPLATE_RESULT);
expect(templates.request.render).toHaveBeenCalledWith({ source, arguments: args }, context, info);
expect(dataFetcher).toHaveBeenCalledWith(REQUEST_TEMPLATE_RESULT);
expect(getDataLoader).toBeCalledWith('TodoTable');
expect(templates.response.render).toHaveBeenCalledWith({ source, arguments: args, result: DATA_FROM_DATA_SOURCE }, context, info);
});

it('should not call the response mapping template with template version 2017-02-29 and data fetcher throws error', async () => {
dataFetcher.mockImplementation(() => {
throw new Error('Some request template error');
});

await expect(() => resolver.resolve(source, args, context, info)).rejects.toThrowError('Some request template error');
expect(templates.request.render).toHaveBeenCalledWith({ source, arguments: args }, context, info);
expect(dataFetcher).toHaveBeenCalledWith(REQUEST_TEMPLATE_RESULT);
expect(getDataLoader).toBeCalledWith('TodoTable');
expect(templates.response.render).not.toHaveBeenCalled();
});

it('should render response mapping when data fetcher throws error and template version is 2018-05-29', async () => {
REQUEST_TEMPLATE_RESULT.version = '2018-05-29';
const error = new Error('Some request template error');
dataFetcher.mockImplementation(() => {
throw error;
});
const result = await resolver.resolve(source, args, context, info);
expect(result).toEqual(RESPONSE_TEMPLATE_RESULT);
expect(templates.request.render).toHaveBeenCalledWith({ source, arguments: args }, context, info);
expect(dataFetcher).toHaveBeenCalledWith(REQUEST_TEMPLATE_RESULT);
expect(getDataLoader).toBeCalledWith('TodoTable');
expect(templates.response.render).toHaveBeenCalledWith({ source, arguments: args, error: error, result: null }, context, info);
});

it('should not render response mapping template when #return is used in request mapping template', async () => {
REQUEST_TEMPLATE_RESULT.version = '2018-05-29';
templates.request.render.mockReturnValue({
...REQUEST_TEMPLATE_RESULT,
errors: [],
isReturn: true,
});

const result = await resolver.resolve(source, args, context, info);
expect(result).toEqual(REQUEST_TEMPLATE_RESULT.result);
expect(templates.request.render).toHaveBeenCalledWith({ source, arguments: args }, context, info);
expect(dataFetcher).not.toHaveBeenCalledWith(REQUEST_TEMPLATE_RESULT);
expect(templates.response.render).not.toHaveBeenCalled();
});

it('should collect all the errors in context object', async () => {
templates.request.render.mockReturnValue({
...REQUEST_TEMPLATE_RESULT,
errors: ['request error'],
});
templates.response.render.mockReturnValue({
...REQUEST_TEMPLATE_RESULT,
errors: ['response error'],
});
const result = await resolver.resolve(source, args, context, info);
expect(result).toEqual(REQUEST_TEMPLATE_RESULT.result);
expect(context.appsyncErrors).toEqual(['request error', 'response error']);
});
});
});
Loading

0 comments on commit da712b8

Please sign in to comment.