mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
502 lines
20 KiB
JavaScript
502 lines
20 KiB
JavaScript
'use strict';
|
|
|
|
const { expect } = require('chai');
|
|
|
|
const ServerlessError = require('../../../../../lib/serverless-error');
|
|
const resolveMeta = require('../../../../../lib/configuration/variables/resolve-meta');
|
|
const resolve = require('../../../../../lib/configuration/variables/resolve');
|
|
|
|
describe('test/unit/lib/configuration/variables/resolve.test.js', () => {
|
|
describe('Complete resolution', () => {
|
|
const configuration = {
|
|
foo: {
|
|
params: '${sourceParam(param1, param2)}',
|
|
varParam: '${sourceParam(${sourceDirect:})}',
|
|
},
|
|
static: true,
|
|
address: 'foo${sourceAddress:address-result}',
|
|
varAddress: 'foo${sourceAddress:${sourceDirect:}}',
|
|
direct: '${sourceDirect:}',
|
|
property: '${sourceProperty(direct)}',
|
|
escape:
|
|
'e\\${sourceDirect:}n\\$${sourceProperty(address)}qe\\\\\\${sourceProperty(direct)}qn\\\\${sourceProperty(address)}',
|
|
otherProperty: '${sourceProperty(varAddress)}',
|
|
deepProperty: '${sourceProperty(foo)}',
|
|
deepPropertyUnrecognized: '${sourceProperty(nestUnrecognized)}',
|
|
deepPropertyErrored: '${sourceProperty(nestErrored)}',
|
|
staticProperty: '${sourceProperty(static)}',
|
|
propertyUnrecognized: '${sourceProperty(nestUnrecognized, unrecognized)}',
|
|
propertyErrored: '${sourceProperty(nestErrored, erroredAddress)}',
|
|
propertyCircularA: '${sourceProperty(propertyCircularB)}',
|
|
propertyCircularB: '${sourceProperty(propertyCircularA)}',
|
|
propertyDeepCircularA: '${sourceProperty(propertyDeepCircularB)}',
|
|
propertyDeepCircularB: '${sourceProperty(propertyDeepCircularC)}',
|
|
propertyDeepCircularC: '${sourceProperty(propertyDeepCircularA)}',
|
|
propertyRoot: '${sourceProperty:}',
|
|
withString: 'foo${sourceDirect:}',
|
|
resolvesResultVariablesObject: '${sourceResultVariables(object)}',
|
|
resolvesResultVariablesArray: '${sourceResultVariables(array)}',
|
|
resolvesResultVariablesString: '${sourceResultVariables(string)}',
|
|
resolvesResultVariablesStringInvalid: '${sourceResultVariables(stringInvalid)}',
|
|
resolveDeepVariablesConcat:
|
|
'${sourceResultVariables(string)}foo${sourceResultVariables(string)}',
|
|
resolveVariablesInString: "${sourceResolveVariablesInString('${sourceProperty(foo)}')}",
|
|
resolvesVariables: '${sourceResolveVariable("sourceParam(${sourceDirect:})")}',
|
|
resolvesVariablesFallback: '${sourceResolveVariable("sourceMissing:, null"), null}',
|
|
resolvesVariablesInvalid1: '${sourceResolveVariable("sourceDirect(")}',
|
|
resolvesVariablesInvalid2: '${sourceResolveVariable("sourceDirect")}',
|
|
incomplete: '${sourceDirect:}elo${sourceIncomplete:}',
|
|
missing: '${sourceDirect:}elo${sourceMissing:}other${sourceMissing:}',
|
|
missingFallback: '${sourceDirect:}elo${sourceMissing:, "foo"}',
|
|
missingFallbackNull: '${sourceMissing:, null}',
|
|
nonStringStringPart: 'elo${sourceMissing:, null}',
|
|
notExistingProperty: "${sourceProperty(not, existing), 'notExistingFallback'}",
|
|
nestUnrecognized: {
|
|
unrecognized:
|
|
'${sourceDirect:}|${sourceUnrecognized:}|${sourceDirect(${sourceUnrecognized:})}' +
|
|
'${sourceDirect:${sourceUnrecognized:}}',
|
|
},
|
|
erroredParam: '${sourceDirect(${sourceError:})}',
|
|
nestErrored: {
|
|
erroredAddress: '${sourceDirect:${sourceError:}}',
|
|
},
|
|
erroredSourceServerlessError: '${sourceError(serverless-error)}',
|
|
erroredSourceNonServerlessError: '${sourceError:}',
|
|
erroredSourceNonErrorException: '${sourceError(non-error-exception)}|',
|
|
invalidResultCircular: '${sourceError(circular-ref)}',
|
|
invalidResultNonJson: '${sourceError(non-json)}',
|
|
invalidResultNonJsonCircular: '|${sourceError(non-json-circular)}',
|
|
infiniteResolutionRecursion: '${sourceInfinite:}',
|
|
invalidResultValue: '${sourceError(no-value)}',
|
|
sharedSourceResolution1: '${sourceShared:}',
|
|
sharedSourceResolution2: '${sourceProperty(sharedSourceResolution1, sharedFinal)}',
|
|
sharedPropertyResolution1: '${sourceSharedProperty:}',
|
|
sharedPropertyResolution2: '${sourceProperty(sharedPropertyResolution1, sharedFinal)}',
|
|
};
|
|
let variablesMeta;
|
|
const sources = {
|
|
sourceParam: {
|
|
resolve: ({ params }) => ({ value: params.join('|') }),
|
|
},
|
|
sourceAddress: {
|
|
resolve: ({ address }) => ({ value: address }),
|
|
},
|
|
sourceDirect: {
|
|
resolve: () => ({ value: 234 }),
|
|
},
|
|
sourceProperty: {
|
|
resolve: async ({ params, resolveConfigurationProperty }) => {
|
|
const result = await resolveConfigurationProperty(params || []);
|
|
return { value: result == null ? null : result };
|
|
},
|
|
},
|
|
sourceResolveVariable: {
|
|
resolve: async ({ params, resolveVariable }) => {
|
|
return { value: await resolveVariable(params[0]) };
|
|
},
|
|
},
|
|
sourceResolveVariablesInString: {
|
|
resolve: async ({ params, resolveVariablesInString }) => {
|
|
return { value: await resolveVariablesInString(params[0]) };
|
|
},
|
|
},
|
|
sourceResultVariables: {
|
|
resolve: ({ params: [type] }) => {
|
|
switch (type) {
|
|
case 'object':
|
|
return { value: { foo: '${sourceDirect:}' } };
|
|
case 'array':
|
|
return { value: [1, '${sourceDirect:}'] };
|
|
case 'string':
|
|
return { value: '${sourceDirect:}' };
|
|
case 'stringInvalid':
|
|
return { value: '${sourceDirect:' };
|
|
case 'error':
|
|
return { value: [1, '${sourceUnrecognized:}', '${sourceError:}'] };
|
|
default:
|
|
throw new Error('Unexpected');
|
|
}
|
|
},
|
|
},
|
|
sourceIncomplete: {
|
|
resolve: () => ({ value: null, isPending: true }),
|
|
},
|
|
sourceMissing: {
|
|
resolve: () => ({ value: null }),
|
|
},
|
|
sourceError: {
|
|
resolve: ({ params }) => {
|
|
switch (params && params[0]) {
|
|
case 'non-error-exception':
|
|
throw null; // eslint-disable-line no-throw-literal
|
|
case 'serverless-error':
|
|
throw new ServerlessError('Stop');
|
|
case 'circular-ref': {
|
|
const obj = {};
|
|
obj.obj = obj;
|
|
return { value: obj };
|
|
}
|
|
case 'non-json':
|
|
return { value: new Set() };
|
|
case 'non-json-circular': {
|
|
const obj = new Set();
|
|
obj.obj = obj;
|
|
return { value: obj };
|
|
}
|
|
case 'no-value':
|
|
return {};
|
|
default:
|
|
throw new Error('Stop');
|
|
}
|
|
},
|
|
},
|
|
|
|
sourceInfinite: {
|
|
resolve: () => ({ value: { nest: '${sourceInfinite:}' } }),
|
|
},
|
|
sourceShared: {
|
|
resolve: () => ({
|
|
value: {
|
|
sharedFinal: 'foo',
|
|
sharedInner: '${sourceProperty(sharedSourceResolution1, sharedFinal)}',
|
|
},
|
|
}),
|
|
},
|
|
sourceSharedProperty: {
|
|
resolve: () => ({
|
|
value: {
|
|
sharedFinal: 'foo',
|
|
sharedInner: '${sourceProperty(sharedPropertyResolution2)}',
|
|
},
|
|
}),
|
|
},
|
|
};
|
|
before(async () => {
|
|
variablesMeta = resolveMeta(configuration);
|
|
await resolve({
|
|
serviceDir: process.cwd(),
|
|
configuration,
|
|
variablesMeta,
|
|
sources,
|
|
options: {},
|
|
fulfilledSources: new Set(),
|
|
});
|
|
});
|
|
|
|
it('should resolve non-string variable', () => {
|
|
expect(configuration.direct).to.equal(234);
|
|
});
|
|
|
|
it('should resolve variable concatenated with string value', () => {
|
|
expect(configuration.withString).to.equal('foo234');
|
|
});
|
|
|
|
it('should pass params to source resolvers', () => {
|
|
expect(configuration.foo.params).to.equal('param1|param2');
|
|
});
|
|
|
|
it('should pass address to source resolvers', () => {
|
|
expect(configuration.address).to.equal('fooaddress-result');
|
|
});
|
|
|
|
it('should resolve variables in params', () => {
|
|
expect(configuration.foo.varParam).to.equal('234');
|
|
});
|
|
|
|
it('should resolve variables in address', () => {
|
|
expect(configuration.varAddress).to.equal('foo234');
|
|
});
|
|
|
|
it('should allow sources to get values of other properties', () => {
|
|
expect(configuration.property).to.equal(234);
|
|
expect(configuration.otherProperty).to.equal('foo234');
|
|
expect(configuration.static).to.equal(true);
|
|
expect(configuration.deepProperty).to.deep.equal({
|
|
params: 'param1|param2',
|
|
varParam: '234',
|
|
});
|
|
});
|
|
|
|
it('should clear escapes', () => {
|
|
expect(configuration.escape).to.equal(
|
|
'e${sourceDirect:}n\\$fooaddress-resultqe\\${sourceProperty(direct)}qn\\fooaddress-result'
|
|
);
|
|
});
|
|
|
|
it('should support incomplete sources', () => {
|
|
expect(variablesMeta.get('incomplete')).to.have.property('variables');
|
|
});
|
|
|
|
it('should mark with error missing source without fallback', () => {
|
|
const valueMeta = variablesMeta.get('missing');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('MISSING_VARIABLE_RESULT');
|
|
});
|
|
|
|
it('should support fallback on missing source', () => {
|
|
expect(configuration.missingFallback).to.equal('234elofoo');
|
|
});
|
|
|
|
it('should report not existing property with null', () => {
|
|
expect(configuration.notExistingProperty).to.equal('notExistingFallback');
|
|
});
|
|
|
|
it('should support `null` fallback on missing source', () => {
|
|
expect(configuration.missingFallbackNull).to.equal(null);
|
|
});
|
|
|
|
it('should resolve variables in returned results', () => {
|
|
expect(configuration.resolvesResultVariablesObject).to.deep.equal({ foo: 234 });
|
|
expect(configuration.resolvesResultVariablesArray).to.deep.equal([1, 234]);
|
|
expect(configuration.resolvesResultVariablesString).to.equal(234);
|
|
});
|
|
|
|
it('should resolve variables in resolved strings which are subject to concatenation', () => {
|
|
expect(configuration.resolveDeepVariablesConcat).to.equal('234foo234');
|
|
});
|
|
|
|
it('should provide working resolveVariablesInString util', () => {
|
|
expect(configuration.resolveVariablesInString).to.deep.equal({
|
|
params: 'param1|param2',
|
|
varParam: '234',
|
|
});
|
|
});
|
|
|
|
// https://github.com/serverless/serverless/issues/9016
|
|
it('should resolve same sources across resolution batches without shared caching', () => {
|
|
expect(configuration.sharedSourceResolution1).to.deep.equal({
|
|
sharedFinal: 'foo',
|
|
sharedInner: 'foo',
|
|
});
|
|
expect(configuration.sharedSourceResolution2).to.equal('foo');
|
|
});
|
|
|
|
// https://github.com/serverless/serverless/issues/9047
|
|
it('should resolve same properties across resolution batches without shared caching', () => {
|
|
expect(configuration.sharedPropertyResolution1).to.deep.equal({
|
|
sharedFinal: 'foo',
|
|
sharedInner: 'foo',
|
|
});
|
|
expect(configuration.sharedSourceResolution2).to.equal('foo');
|
|
});
|
|
|
|
it('should not resolve variables for unrecognized sources', () => {
|
|
expect(variablesMeta.get('nestUnrecognized\0unrecognized')).to.have.property('variables');
|
|
});
|
|
|
|
it('should error or non stringifiable value as part of a string', () => {
|
|
const valueMeta = variablesMeta.get('nonStringStringPart');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('NON_STRING_VARIABLE_RESULT');
|
|
});
|
|
|
|
it('should mark errored resolution in param with error', () => {
|
|
const valueMeta = variablesMeta.get('erroredParam');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should mark errored resolution in address with error', () => {
|
|
const valueMeta = variablesMeta.get('nestErrored\0erroredAddress');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should mark ServerlessError errored resolution with error', () => {
|
|
const valueMeta = variablesMeta.get('erroredSourceServerlessError');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should mark non ServerlessError errored resolution with error', () => {
|
|
const valueMeta = variablesMeta.get('erroredSourceNonServerlessError');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should mark non error exception errored resolution with error', () => {
|
|
const valueMeta = variablesMeta.get('erroredSourceNonErrorException');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should mark json result with circular references with error', () => {
|
|
const valueMeta = variablesMeta.get('invalidResultCircular');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should mark non json result with error', () => {
|
|
const valueMeta = variablesMeta.get('invalidResultNonJson');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should mark circular dependency among properties with error', () => {
|
|
const valueMeta = variablesMeta.get('propertyCircularA');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should mark deep circular dependency among properties with error', () => {
|
|
const valueMeta = variablesMeta.get('propertyDeepCircularA');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should mark property root reference with error', () => {
|
|
const valueMeta = variablesMeta.get('propertyRoot');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should not resolve dependency on unresolved property', () => {
|
|
const valueMeta = variablesMeta.get('deepPropertyUnrecognized');
|
|
expect(valueMeta).to.have.property('variables');
|
|
});
|
|
|
|
it('should mark dependency on errored property with error', () => {
|
|
const valueMeta = variablesMeta.get('deepPropertyErrored');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should error on infinite resolution recursion', () => {
|
|
const valueMeta = variablesMeta.get(`infiniteResolutionRecursion${'\0nest'.repeat(10)}`);
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('EXCESSIVE_RESOLVED_PROPERTIES_NEST_DEPTH');
|
|
});
|
|
|
|
it('should error on invalid variable notation in returned result', () => {
|
|
const valueMeta = variablesMeta.get('resolvesResultVariablesStringInvalid');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('UNTERMINATED_VARIABLE');
|
|
});
|
|
|
|
it('should error on invalid source resolution resolt', () => {
|
|
const valueMeta = variablesMeta.get('invalidResultValue');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
|
|
it('should allow to re-resolve fulfilled sources', async () => {
|
|
await resolve({
|
|
serviceDir: process.cwd(),
|
|
configuration,
|
|
variablesMeta,
|
|
sources: { ...sources, sourceIncomplete: { resolve: () => ({ value: 'complete' }) } },
|
|
options: {},
|
|
fulfilledSources: new Set(),
|
|
});
|
|
expect(configuration.incomplete).to.equal('234elocomplete');
|
|
});
|
|
|
|
it('should remove from variables meta data on resolved properties', () => {
|
|
expect(Array.from(variablesMeta.keys())).to.deep.equal([
|
|
'deepPropertyUnrecognized',
|
|
'deepPropertyErrored',
|
|
'propertyUnrecognized',
|
|
'propertyErrored',
|
|
'propertyCircularA',
|
|
'propertyCircularB',
|
|
'propertyDeepCircularA',
|
|
'propertyDeepCircularB',
|
|
'propertyDeepCircularC',
|
|
'propertyRoot',
|
|
'resolvesResultVariablesStringInvalid',
|
|
'resolvesVariablesInvalid1',
|
|
'resolvesVariablesInvalid2',
|
|
'missing',
|
|
'nonStringStringPart',
|
|
'nestUnrecognized\0unrecognized',
|
|
'erroredParam',
|
|
'nestErrored\0erroredAddress',
|
|
'erroredSourceServerlessError',
|
|
'erroredSourceNonServerlessError',
|
|
'erroredSourceNonErrorException',
|
|
'invalidResultCircular',
|
|
'invalidResultNonJson',
|
|
'invalidResultNonJsonCircular',
|
|
'invalidResultValue',
|
|
`infiniteResolutionRecursion${'\0nest'.repeat(10)}`,
|
|
]);
|
|
});
|
|
|
|
describe('"resolveVariable" source util', () => {
|
|
it('should resolve variable', () => {
|
|
expect(configuration.resolvesVariables).to.equal('234');
|
|
});
|
|
it('should support multiple sources', () => {
|
|
expect(configuration.resolvesVariablesFallback).to.equal(null);
|
|
});
|
|
|
|
it('should error on invalid input', () => {
|
|
let valueMeta = variablesMeta.get('resolvesVariablesInvalid1');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
|
|
valueMeta = variablesMeta.get('resolvesVariablesInvalid2');
|
|
expect(valueMeta).to.not.have.property('variables');
|
|
expect(valueMeta.error.code).to.equal('VARIABLE_RESOLUTION_ERROR');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Partial resolution', () => {
|
|
const configuration = {
|
|
static: true,
|
|
obj: {
|
|
child: 'marko',
|
|
},
|
|
behindVar: '${sourceDirect:}',
|
|
direct: '${sourceProperty(behindVar)}',
|
|
childParent: '${sourceProperty(obj)}',
|
|
parent: {
|
|
parentChild: '${sourceProperty(behindVar)}',
|
|
},
|
|
unresolved: '${sourceProperty(static)}',
|
|
};
|
|
let variablesMeta;
|
|
const sources = {
|
|
sourceDirect: {
|
|
resolve: () => ({ value: 234 }),
|
|
},
|
|
sourceProperty: {
|
|
resolve: async ({ params, resolveConfigurationProperty }) => {
|
|
const result = await resolveConfigurationProperty(params || []);
|
|
return { value: result == null ? null : result };
|
|
},
|
|
},
|
|
};
|
|
before(async () => {
|
|
variablesMeta = resolveMeta(configuration);
|
|
await resolve({
|
|
serviceDir: process.cwd(),
|
|
configuration,
|
|
variablesMeta,
|
|
sources,
|
|
options: {},
|
|
fulfilledSources: new Set(),
|
|
propertyPathsToResolve: new Set(['direct', 'childParent\0child', 'parent']),
|
|
});
|
|
});
|
|
|
|
it('should resolve directly pointed property', () => {
|
|
expect(configuration.direct).to.equal(234);
|
|
});
|
|
it('should resolve dependency of pointed property', () => {
|
|
expect(configuration.behindVar).to.equal(234);
|
|
});
|
|
it('should resolve parent of pointed property', () => {
|
|
expect(configuration.childParent.child).to.equal('marko');
|
|
});
|
|
it('should resolve child of pointed property', () => {
|
|
expect(configuration.parent).to.deep.equal({ parentChild: 234 });
|
|
});
|
|
it('should note resolve not pointed properties', () => {
|
|
const valueMeta = variablesMeta.get('unresolved');
|
|
expect(valueMeta).to.have.property('variables');
|
|
});
|
|
});
|
|
});
|