serverless-step-functions
Version:
The module is AWS Step Functions plugin for Serverless Framework
826 lines (760 loc) • 22.3 kB
JavaScript
const expect = require('chai').expect;
const Serverless = require('serverless/lib/Serverless');
const AwsProvider = require('serverless/lib/plugins/aws/provider');
const ServerlessStepFunctions = require('./../../../index');
describe('#httpValidate()', () => {
let serverless;
let serverlessStepFunctions;
beforeEach(() => {
serverless = new Serverless();
serverless.setProvider('aws', new AwsProvider(serverless));
serverless.configSchemaHandler = {
// eslint-disable-next-line no-unused-vars
defineTopLevelProperty: (propertyName, propertySchema) => {},
};
const options = {
stage: 'dev',
region: 'us-east-1',
};
serverlessStepFunctions = new ServerlessStepFunctions(serverless, options);
});
it('should ignore non-http events', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
ignored: {},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(0);
});
it('should reject an invalid http event', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: true,
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should throw an error if http event type is not a string or an object', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: 42,
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should validate the http events "path" property', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'POST',
},
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should validate the http events "method" property', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
path: 'foo/bar',
},
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should validate the http events object syntax method is case insensitive', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'POST',
path: 'foo/bar',
},
},
{
http: {
method: 'post',
path: 'foo/bar',
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(2);
});
it('should validate the http events string syntax method is case insensitive', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: 'POST foo/bar',
},
{
http: 'post foo/bar',
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(2);
});
it('should throw an error if the method is invalid', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
path: 'foo/bar',
method: 'INVALID',
},
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should filter non-http events', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
},
},
{},
],
},
second: {
events: [
{
other: {},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(1);
});
it('should discard a starting slash from paths', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'POST',
path: '/foo/bar',
},
},
{
http: 'GET /foo/bar',
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(2);
expect(validated.events[0].http).to.have.property('path', 'foo/bar');
expect(validated.events[1].http).to.have.property('path', 'foo/bar');
});
it('should throw if an authorizer is an invalid value', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: true,
},
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should throw if an authorizer is an empty object', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: {},
},
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should throw if an cognito claims are being with a lambda proxy', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
integration: 'lambda-proxy',
authorizer: {
arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ',
claims: [
'email',
'nickname',
],
},
},
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should throw if an integration is not supported', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
integration: 'fake',
authorizer: {
arn: 'arn:aws:cognito-idp:us-east-1:xxx:userpool/us-east-1_ZZZ',
},
},
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should accept AWS_IAM as authorizer', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
foo: {},
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: 'aws_iam',
},
},
],
},
second: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: {
type: 'aws_iam',
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(2);
expect(validated.events[0].http.authorizer.type).to.equal('AWS_IAM');
expect(validated.events[1].http.authorizer.type).to.equal('AWS_IAM');
});
it('should accept an authorizer as a string', () => {
serverlessStepFunctions.serverless.service.functions = {
foo: {},
};
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: 'foo',
},
},
],
},
second: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: 'sss:dev-authorizer',
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(2);
expect(validated.events[0].http.authorizer.name).to.equal('foo');
expect(validated.events[0].http.authorizer.arn).to.deep.equal({
'Fn::GetAtt': [
'FooLambdaFunction',
'Arn',
],
});
expect(validated.events[1].http.authorizer.name).to.equal('authorizer');
expect(validated.events[1].http.authorizer.arn).to.equal('sss:dev-authorizer');
});
it('should set authorizer defaults', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: {
arn: 'sss:dev-authorizer',
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
const authorizer = validated.events[0].http.authorizer;
expect(authorizer.resultTtlInSeconds).to.equal(300);
expect(authorizer.identitySource).to.equal('method.request.header.Authorization');
});
it('should accept authorizer config', () => {
serverlessStepFunctions.serverless.service.functions = {
foo: {},
};
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
integration: 'LAMBDA',
authorizer: {
name: 'foo',
resultTtlInSeconds: 500,
identitySource: 'method.request.header.Custom',
identityValidationExpression: 'foo',
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
const authorizer = validated.events[0].http.authorizer;
expect(authorizer.resultTtlInSeconds).to.equal(500);
expect(authorizer.identitySource).to.equal('method.request.header.Custom');
expect(authorizer.identityValidationExpression).to.equal('foo');
});
it('should accept authorizer config with scopes', () => {
serverlessStepFunctions.serverless.service.functions = {
foo: {},
};
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
integration: 'MOCK',
authorizer: {
name: 'authorizer',
arn: 'arn:aws:cognito-idp:eu-west-1:xxxxxxxxxx',
identitySouce: 'method.request.header.Authorization',
scopes: ['scope1', 'scope2'],
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
const authorizer = validated.events[0].http.authorizer;
expect(authorizer.scopes).to.deep.equal(['scope1', 'scope2']);
});
it('should accept authorizer config with a type', () => {
serverlessStepFunctions.serverless.service.functions = {
foo: {},
};
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
integration: 'MOCK',
authorizer: {
name: 'foo',
type: 'request',
resultTtlInSeconds: 500,
identitySource: 'method.request.header.Custom',
identityValidationExpression: 'foo',
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
const authorizer = validated.events[0].http.authorizer;
expect(authorizer.type).to.equal('request');
});
it('should accept authorizer config with a type and authorizerId', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: {
name: 'foo',
type: 'CUSTOM',
authorizerId: '12345',
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events[0].http.authorizer.type).to.equal('CUSTOM');
expect(validated.events[0].http.authorizer.authorizerId).to.equal('12345');
});
it('should accept authorizer config with a lambda arn', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: {
name: 'foo',
arn: 'xxx:xxx:Lambda-Name',
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events[0].http.authorizer.arn).to.equal('xxx:xxx:Lambda-Name');
});
it('should accept authorizer config when resultTtlInSeconds is 0', () => {
serverlessStepFunctions.serverless.service.functions = {
foo: {},
};
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
foo: {},
first: {
events: [
{
http: {
method: 'GET',
path: 'foo/bar',
authorizer: {
name: 'foo',
resultTtlInSeconds: 0,
identitySource: 'method.request.header.Custom',
identityValidationExpression: 'foo',
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
const authorizer = validated.events[0].http.authorizer;
expect(authorizer.resultTtlInSeconds).to.equal(0);
expect(authorizer.identitySource).to.equal('method.request.header.Custom');
expect(authorizer.identityValidationExpression).to.equal('foo');
});
it('should throw an error if "origin" and "origins" CORS config is used', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'POST',
path: '/foo/bar',
cors: {
origin: '*',
origins: ['*'],
},
},
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate())
.to.throw(Error, 'can only use');
});
it('should process cors defaults', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'POST',
path: '/foo/bar',
cors: true,
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(1);
expect(validated.events[0].http.cors).to.deep.equal({
headers: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key',
'X-Amz-Security-Token', 'X-Amz-User-Agent'],
methods: ['OPTIONS', 'POST'],
origin: '*',
origins: ['*'],
allowCredentials: false,
});
});
it('should throw if cors headers are not an array', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'POST',
path: '/foo/bar',
cors: {
headers: true,
},
},
},
],
},
},
};
expect(() => serverlessStepFunctions.httpValidate()).to.throw(Error);
});
it('should process cors options', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'POST',
path: '/foo/bar',
cors: {
headers: ['X-Foo-Bar'],
origins: ['acme.com'],
methods: ['POST', 'OPTIONS'],
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(1);
expect(validated.events[0].http.cors).to.deep.equal({
headers: ['X-Foo-Bar'],
methods: ['POST', 'OPTIONS'],
origins: ['acme.com'],
allowCredentials: false,
});
});
it('should merge all preflight origins, method, headers and allowCredentials for a path', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'GET',
path: 'users',
cors: {
origins: [
'http://example.com',
],
allowCredentials: true,
},
},
}, {
http: {
method: 'POST',
path: 'users',
cors: {
origins: [
'http://example2.com',
],
},
},
}, {
http: {
method: 'PUT',
path: 'users/{id}',
cors: {
headers: [
'TestHeader',
],
},
},
}, {
http: {
method: 'DELETE',
path: 'users/{id}',
cors: {
headers: [
'TestHeader2',
],
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.corsPreflight['users/{id}'].methods)
.to.deep.equal(['OPTIONS', 'DELETE', 'PUT']);
expect(validated.corsPreflight.users.origins)
.to.deep.equal(['http://example2.com', 'http://example.com']);
expect(validated.corsPreflight['users/{id}'].headers)
.to.deep.equal(['TestHeader2', 'TestHeader']);
expect(validated.corsPreflight.users.allowCredentials)
.to.equal(true);
expect(validated.corsPreflight['users/{id}'].allowCredentials)
.to.equal(false);
});
it('serverlessStepFunctions', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'POST',
path: '/foo/bar',
cors: {
methods: ['POST'],
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events).to.be.an('Array').with.length(1);
expect(validated.events[0].http.cors.methods).to.deep.equal(['POST', 'OPTIONS']);
});
it('should set cors Access-Control-Max-Age headers', () => {
serverlessStepFunctions.serverless.service.stepFunctions = {
stateMachines: {
first: {
events: [
{
http: {
method: 'POST',
path: '/foo/bar',
cors: {
origin: '*',
maxAge: 86400,
},
},
},
],
},
},
};
const validated = serverlessStepFunctions.httpValidate();
expect(validated.events[0].http.cors.origin).to.equal('*');
expect(validated.events[0].http.cors.maxAge).to.equal(86400);
});
});
;