572 lines
18 KiB
JavaScript

'use strict';
const { expect } = require('chai');
const ServerlessError = require('../../../../../lib/serverless-error');
const parse = require('../../../../../lib/configuration/variables/parse');
describe('test/unit/lib/configuration/variables/parse.test.js', () => {
describe('Valid', () => {
it('should support partially variable value at begin of a string', () =>
expect(parse('${type:address}foo')).to.deep.equal([
{ start: 0, end: 15, sources: [{ type: 'type', address: { value: 'address' } }] },
]));
it('should support partially variable value at end of a string', () =>
expect(parse('foo${type:address}')).to.deep.equal([
{ start: 3, end: 18, sources: [{ type: 'type', address: { value: 'address' } }] },
]));
it('should support partially variable value in a middle of a string', () =>
expect(parse('foo${type:address}bar')).to.deep.equal([
{ start: 3, end: 18, sources: [{ type: 'type', address: { value: 'address' } }] },
]));
// ${type:}
it('should support type only notation', () =>
expect(parse('${type:}')).to.deep.equal([{ sources: [{ type: 'type' }] }]));
// ${type:address}
it('should support type and address', () =>
expect(parse('${type:address}')).to.deep.equal([
{ sources: [{ type: 'type', address: { value: 'address' } }] },
]));
// ${type:address:with:colons}
it('should support type and address with colons', () =>
expect(parse('${type:address:with:colons}')).to.deep.equal([
{ sources: [{ type: 'type', address: { value: 'address:with:colons' } }] },
]));
// ${type(param)}
it('should support param', () =>
expect(parse('${type(param)}')).to.deep.equal([
{ sources: [{ type: 'type', params: [{ value: 'param' }] }] },
]));
// ${type(param1, param2)}
it('should support multiple params', () =>
expect(parse('${type(param1, param2)}')).to.deep.equal([
{ sources: [{ type: 'type', params: [{ value: 'param1' }, { value: 'param2' }] }] },
]));
// ${type(param1, ",},${\"param2", param3)}
it('should support double quoted params', () =>
expect(parse('${type(param1, ",},${\\"param2", param3)}')).to.deep.equal([
{
sources: [
{
type: 'type',
params: [{ value: 'param1' }, { value: ',},${"param2' }, { value: 'param3' }],
},
],
},
]));
// ${type(param1, ',},${\'param2' )}
it('should support single quoted params', () =>
expect(parse("${type(param1, ',},${param2' )}")).to.deep.equal([
{
sources: [
{
type: 'type',
params: [{ value: 'param1' }, { value: ',},${param2' }],
},
],
},
]));
// ${type(param1, 232, param3)}
it('should support number params', () =>
expect(parse('${type(param1, 232, param3)}')).to.deep.equal([
{
sources: [
{
type: 'type',
params: [{ value: 'param1' }, { value: 232 }, { value: 'param3' }],
},
],
},
]));
// ${type(param1, true, param3)}
it('should support boolean params', () =>
expect(parse('${type(param1, true, param3)}')).to.deep.equal([
{
sources: [
{
type: 'type',
params: [{ value: 'param1' }, { value: true }, { value: 'param3' }],
},
],
},
]));
// ${type(param1, null, param3) }
it('should support null params', () =>
expect(parse('${type(param1, null, param3) }')).to.deep.equal([
{
sources: [
{
type: 'type',
params: [{ value: 'param1' }, { value: null }, { value: 'param3' }],
},
],
},
]));
// ${type(param):address}
it('should support param and address', () =>
expect(parse('${type(param):address}')).to.deep.equal([
{
sources: [{ type: 'type', params: [{ value: 'param' }], address: { value: 'address' } }],
},
]));
// ${type(param):",},${\"address"}
it('should support quoted address', () =>
expect(parse('${type(param):",},${\\"address"}')).to.deep.equal([
{
sources: [
{ type: 'type', params: [{ value: 'param' }], address: { value: ',},${"address' } },
],
},
]));
// ${type(param):'}$address' }
it('should support single quoted address', () =>
expect(parse("${type(param):'}$address' }")).to.deep.equal([
{
sources: [
{ type: 'type', params: [{ value: 'param' }], address: { value: '}$address' } },
],
},
]));
// ${type1(param1), type2(param2):address2, type3:, type4:address4}
it('should support fallback sources', () =>
expect(
parse('${type1(param1), type2(param2):"address2", type3:, type4:address4}')
).to.deep.equal([
{
sources: [
{ type: 'type1', params: [{ value: 'param1' }] },
{ type: 'type2', params: [{ value: 'param2' }], address: { value: 'address2' } },
{ type: 'type3' },
{ type: 'type4', address: { value: 'address4' } },
],
},
]));
// ${type(param), "foo, bar"}
it('should support double quoted string as fallback source', () =>
expect(parse('${type(param), "foo, bar"}')).to.deep.equal([
{
sources: [{ type: 'type', params: [{ value: 'param' }] }, { value: 'foo, bar' }],
},
]));
// ${type(param), 'foo, bar' }
it('should support single quoted string as fallback source', () => {
expect(parse("${type(param), 'foo, bar' }")).to.deep.equal([
{
sources: [{ type: 'type', params: [{ value: 'param' }] }, { value: 'foo, bar' }],
},
]);
});
// ${type(param), 232}
it('should support number as a fallback source', () =>
expect(parse('${type(param), 232}')).to.deep.equal([
{
sources: [{ type: 'type', params: [{ value: 'param' }] }, { value: 232 }],
},
]));
// ${type(param), true}
it('should support bolean "true" as a fallback source', () =>
expect(parse('${type(param), true}')).to.deep.equal([
{
sources: [{ type: 'type', params: [{ value: 'param' }] }, { value: true }],
},
]));
// ${type(param), false }
it('should support bolean "false" as a fallback source', () =>
expect(parse('${type(param), false }')).to.deep.equal([
{
sources: [{ type: 'type', params: [{ value: 'param' }] }, { value: false }],
},
]));
// ${type(param), null}
it('should support null as a fallback source', () =>
expect(parse('${type(param), null}')).to.deep.equal([
{
sources: [{ type: 'type', params: [{ value: 'param' }] }, { value: null }],
},
]));
// ${type()}
it('should support empty parens', () =>
expect(parse('${type()}')).to.deep.equal([
{
sources: [{ type: 'type', params: [] }],
},
]));
// ${type(\t)}
it('should ignore any whitespace between brackets', () =>
expect(parse('${type(\t)}')).to.deep.equal([{ sources: [{ type: 'type', params: [] }] }]));
// ${type(,,)}
it('should support skipping arguments', () =>
expect(parse('${type(,,)}')).to.deep.equal([
{
sources: [{ type: 'type', params: [{ value: null }, { value: null }] }],
},
]));
// ${type(${innerType(innerParam, ${deep:}):innerAddress}, foo${bar:}): address}
it('should support variables in params', () =>
expect(
parse('${type(${innerType(innerParam, ${deep:}):innerAddress}, foo${bar:}): address}')
).to.deep.equal([
{
sources: [
{
type: 'type',
params: [
{
value: '${innerType(innerParam, ${deep:}):innerAddress}',
variables: [
{
sources: [
{
type: 'innerType',
params: [
{ value: 'innerParam' },
{ value: '${deep:}', variables: [{ sources: [{ type: 'deep' }] }] },
],
address: { value: 'innerAddress' },
},
],
},
],
},
{
value: 'foo${bar:}',
variables: [
{
start: 3,
end: 10,
sources: [{ type: 'bar' }],
},
],
},
],
address: { value: 'address' },
},
],
},
]));
// ${type(params):${innerType(innerParam):innerAddress}}
// ${type(param):foo${innerType(innerParam)}}
it('should support variables in address', () => {
expect(parse('${type(param):${innerType(innerParam):innerAddress}}')).to.deep.equal([
{
sources: [
{
type: 'type',
params: [{ value: 'param' }],
address: {
value: '${innerType(innerParam):innerAddress}',
variables: [
{
sources: [
{
type: 'innerType',
params: [{ value: 'innerParam' }],
address: { value: 'innerAddress' },
},
],
},
],
},
},
],
},
]);
expect(parse('${type(param):foo${innerType(innerParam)}}')).to.deep.equal([
{
sources: [
{
type: 'type',
params: [{ value: 'param' }],
address: {
value: 'foo${innerType(innerParam)}',
variables: [
{
start: 3,
end: 27,
sources: [
{
type: 'innerType',
params: [{ value: 'innerParam' }],
},
],
},
],
},
},
],
},
]);
});
// ${type.dot(param)}
it('should support dots in type notation', () =>
expect(parse('${type.dot(param)}')).to.deep.equal([
{ sources: [{ type: 'type.dot', params: [{ value: 'param' }] }] },
]));
// ${type.us-east-1(param)}
it('should support hyphens in type notation', () =>
expect(parse('${type.us-east-1(param)}')).to.deep.equal([
{ sources: [{ type: 'type.us-east-1', params: [{ value: 'param' }] }] },
]));
// ${AWS::${type:address}}
it('should support variable nested in foreign variable', () =>
expect(parse('${AWS::${type:address}}')).to.deep.equal([
{ start: 7, end: 22, sources: [{ type: 'type', address: { value: 'address' } }] },
]));
// ${sourceDirect:}elo${sourceIncomplete:}
it('should support multiple variables in a value', () =>
expect(parse('${type1:}elo${type2:}')).to.deep.equal([
{ start: 0, end: 9, sources: [{ type: 'type1' }] },
{ start: 12, end: 21, sources: [{ type: 'type2' }] },
]));
// ${s:${s:}, 1}
// https://github.com/serverless/serverless/issues/8999
it("should recognize variables in address, if it's followed by source", () =>
expect(parse('${s:${s:}, 1}')).to.deep.equal([
{
sources: [
{
type: 's',
address: {
value: '${s:}',
variables: [{ sources: [{ type: 's' }] }],
},
},
{ value: 1 },
],
},
]));
// ${s:, s:${s:}}
// https://github.com/serverless/serverless/issues/9010
it('should resolve nested sources, when at least one parent source was resolved', () =>
expect(parse('${s:, s:${s:}}')).to.deep.equal([
{
sources: [
{ type: 's' },
{
type: 's',
address: {
value: '${s:}',
variables: [{ sources: [{ type: 's' }] }],
},
},
],
},
]));
});
describe('Invalid', () => {
// ${type(${invalid.notation}):address}
it('should reject invalid configuration in params', () =>
expect(() => parse('${type(${invalid.notation}):address}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_TYPE'));
// ${type(params):${innerType(innerParam):${sdfs.fefef}
it('should reject invalid configuration in address', () =>
expect(() => parse('${type(params):${innerType(innerParam):${sdfs.fefef}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_TYPE'));
// ${type:address
it('should detect not closed variable', () => {
expect(() => parse('${type:address'))
.to.throw(ServerlessError)
.with.property('code', 'UNTERMINATED_VARIABLE');
expect(() => parse('${type(foo)'))
.to.throw(ServerlessError)
.with.property('code', 'UNTERMINATED_VARIABLE');
expect(() => parse('${s:, s:${s:}'))
.to.throw(ServerlessError)
.with.property('code', 'UNTERMINATED_VARIABLE');
expect(() => parse('${s:, s:${s:'))
.to.throw(ServerlessError)
.with.property('code', 'UNTERMINATED_VARIABLE');
});
// ${type("\u")}
it('should reject invalid string literal', () =>
expect(() => parse('${type("\\u")}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_STRING_LITERAL'));
// '${type:foo, elo}
it('should reject invalid source literal', () =>
expect(() => parse('${type:foo, elo}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_LITERAL_SOURCE'));
// ${type('foo')bar}
it('should reject missing colon for address', () =>
expect(() => parse("${type('foo')bar}"))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_ADDRESS'));
// ${type:"address"marko}
it('should reject invalid address configuration', () =>
expect(() => parse('${type:"address"marko}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_ADDRESS'));
// ${type:foo, ---}
// ${type:foo, 000:}
// ${type:foo, 000()}
// ${type:foo, aa--}
it('should reject invalid following source', () => {
expect(() => parse('${type:foo, ___}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_SOURCE');
expect(() => parse('${type:foo, --}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_LITERAL_SOURCE');
expect(() => parse('${type:foo, 000:}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_SOURCE');
expect(() => parse('${type:foo, 000()}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_SOURCE');
expect(() => parse('${type:foo, aa--}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_LITERAL_SOURCE');
expect(() => parse('${type:foo, aa__}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_SOURCE');
expect(() => parse('${type:foo,"dev",20}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_SOURCE');
});
// ${type(${AWS::Region})}
// ${type(${foo::Region})}
it('should reject nested foreign variables', () => {
expect(() => parse('${type(${AWS::Region})}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_TYPE');
expect(() => parse('${type(${foo::Region})}'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_ADDRESS');
});
// ${type(foo})
// ${type(foo,})
it('should reject closing bracket at unexpected location', () => {
expect(() => parse('${type(foo})'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_PARAM');
expect(() => parse('${type(foo,})'))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_PARAM');
});
// ${type('foo'bar)}
it('should reject unexpected content after param string', () =>
expect(() => parse("${type('foo'bar)}"))
.to.throw(ServerlessError)
.with.property('code', 'INVALID_VARIABLE_PARAM'));
});
describe('Foreign', () => {
// ${}
it('should ignore empty value', () => expect(parse('${}')).to.equal(null));
// ${${AWS::Region}}
it('should ignore nested foreign notations', () =>
expect(parse('${${AWS::Region}}')).to.equal(null));
// ${type}
it('should ignore just type name string', () => expect(parse('${type}')).to.equal(null));
// ${AWS::Region}
// ${foo::Region}
it('should ignore double clon vars ', () => {
expect(parse('${AWS::Region}')).to.equal(null);
expect(parse('${foo::Region}')).to.equal(null);
});
// ${Database}
// ${stageVariables.stageName}
it('should ignore AWS CF references', () => {
expect(parse('${Database}')).to.equal(null);
expect(parse('foo ${stageVariables.stageName} var')).to.equal(null);
});
// ${sour${inner.type}ce}
it('should ignore nested not supported notations', () =>
expect(parse('fo${bla${foo}}o ${so,ur${inner.type}ce} var')).to.equal(null));
});
describe('Not used', () => {
// foo bar ()
it('should return null', () => expect(parse('fo$o b$$ar ()')).to.equal(null));
// foo\${elo:}
it('should support escape character', () =>
expect(parse('e\\${s:}n\\$${s:}qe\\\\\\${s:}qn\\\\${s:}')).to.deep.equal([
{
start: 1,
end: 3,
value: '$',
},
{
start: 10,
end: 15,
sources: [{ type: 's' }],
},
{
start: 17,
end: 21,
value: '\\$',
},
{
start: 27,
end: 29,
value: '\\',
},
{
start: 29,
end: 34,
sources: [{ type: 's' }],
},
]));
});
});