From b30a2277a78ee8a5ebcf82c4921d2ac26b5bbfaa Mon Sep 17 00:00:00 2001 From: Mariusz Nowak Date: Wed, 10 Nov 2021 16:08:18 +0100 Subject: [PATCH] test: Upgrade `@serverless/test` to v9 --- package.json | 2 +- test/unit/lib/classes/Error.test.js | 18 - test/unit/lib/classes/Variables.test.js | 2915 ----------------- test/unit/lib/cli/handle-error.test.js | 144 +- .../interactive-setup/aws-credentials.test.js | 88 +- .../interactive-setup/dashboard-login.test.js | 23 +- .../dashboard-set-org.test.js | 274 +- .../lib/cli/interactive-setup/deploy.test.js | 80 +- .../lib/cli/interactive-setup/service.test.js | 46 - test/unit/lib/cli/render-help/command.test.js | 34 +- test/unit/lib/cli/render-help/general.test.js | 14 +- test/unit/lib/cli/render-help/index.test.js | 56 +- .../cli/render-help/interactive-setup.test.js | 14 +- test/unit/lib/cli/render-help/options.test.js | 37 +- test/unit/lib/cli/render-version.test.js | 12 +- test/unit/lib/configSchema.test.js | 4 +- .../aws/deploy/lib/checkForChanges.test.js | 25 - .../aws/deploy/lib/extendedValidate.test.js | 44 - .../lib/plugins/aws/deployFunction.test.js | 25 +- test/unit/lib/plugins/aws/invoke.test.js | 4 +- .../lib/plugins/aws/invokeLocal/index.test.js | 62 +- .../aws/package/compile/functions.test.js | 3 +- test/unit/lib/plugins/aws/provider.test.js | 35 - .../unit/lib/plugins/aws/remove/index.test.js | 15 - test/unit/lib/plugins/print.test.js | 4 +- test/unit/lib/utils/logDeprecation.test.js | 137 +- 26 files changed, 235 insertions(+), 3880 deletions(-) delete mode 100644 test/unit/lib/classes/Error.test.js delete mode 100644 test/unit/lib/classes/Variables.test.js diff --git a/package.json b/package.json index 75155b9c7..08c4e76df 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "devDependencies": { "@commitlint/cli": "^12.1.4", "@serverless/eslint-config": "^4.0.0", - "@serverless/test": "^8.8.0", + "@serverless/test": "^9.0.0", "adm-zip": "^0.5.9", "chai": "^4.3.4", "chai-as-promised": "^7.1.1", diff --git a/test/unit/lib/classes/Error.test.js b/test/unit/lib/classes/Error.test.js deleted file mode 100644 index ddd47cbeb..000000000 --- a/test/unit/lib/classes/Error.test.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const overrideStdoutWrite = require('process-utils/override-stdout-write'); -const { logWarning } = require('../../../../lib/classes/Error'); - -describe('#logWarning()', () => { - it('should log warning and proceed', () => { - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => logWarning('a message') - ); - - expect(stdoutData).to.have.string('Serverless Warning'); - expect(stdoutData).to.have.string('a message'); - }); -}); diff --git a/test/unit/lib/classes/Variables.test.js b/test/unit/lib/classes/Variables.test.js deleted file mode 100644 index 2bf500195..000000000 --- a/test/unit/lib/classes/Variables.test.js +++ /dev/null @@ -1,2915 +0,0 @@ -'use strict'; - -/* eslint-disable no-unused-expressions */ - -const BbPromise = require('bluebird'); -const chai = require('chai'); -const jc = require('json-cycle'); -const os = require('os'); -const path = require('path'); -const proxyquire = require('proxyquire'); -const sinon = require('sinon'); -const yaml = require('js-yaml'); -const _ = require('lodash'); -const overrideEnv = require('process-utils/override-env'); - -const AwsProvider = require('../../../../lib/plugins/aws/provider'); -const fse = require('fs-extra'); -const Serverless = require('../../../../lib/Serverless'); -const slsError = require('../../../../lib/classes/Error'); -const Utils = require('../../../../lib/classes/Utils'); -const Variables = require('../../../../lib/classes/Variables'); -const ServerlessError = require('../../../../lib/serverless-error'); -const { getTmpDirPath } = require('../../../utils/fs'); -const skipOnDisabledSymlinksInWindows = require('@serverless/test/skip-on-disabled-symlinks-in-windows'); -const runServerless = require('../../../utils/run-serverless'); - -BbPromise.longStackTraces(true); - -chai.use(require('chai-as-promised')); -chai.use(require('sinon-chai')); - -chai.should(); - -const expect = chai.expect; - -describe('Variables', () => { - let serverless; - let restoreEnv; - - beforeEach(() => { - ({ restoreEnv } = overrideEnv()); - serverless = new Serverless({ - commands: ['print'], - options: {}, - serviceDir: process.cwd(), - configuration: {}, - configurationFilename: 'serverless.yml', - }); - }); - - const afterCallback = () => restoreEnv(); - afterEach(afterCallback); - - describe('#constructor()', () => { - it('should attach serverless instance', () => { - const variablesInstance = new Variables(serverless); - expect(variablesInstance.serverless).to.equal(serverless); - }); - it('should not set variableSyntax in constructor', () => { - const variablesInstance = new Variables(serverless); - expect(variablesInstance.variableSyntax).to.be.undefined; - }); - }); - - describe('#loadVariableSyntax()', () => { - it('should set variableSyntax', () => { - // eslint-disable-next-line no-template-curly-in-string - serverless.service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*?]+?)}}'; - serverless.variables.loadVariableSyntax(); - expect(serverless.variables.variableSyntax).to.be.a('RegExp'); - }); - }); - - describe('#populateService()', () => { - it('should remove problematic attributes bofore calling populateObjectImpl with the service', () => { - const prepopulateServiceStub = sinon - .stub(serverless.variables, 'prepopulateService') - .returns(BbPromise.resolve()); - const populateObjectStub = sinon - .stub(serverless.variables, 'populateObjectImpl') - .callsFake((val) => { - expect(val).to.equal(serverless.service); - expect(val.provider.variableSyntax).to.be.undefined; - expect(val.serverless).to.be.undefined; - return BbPromise.resolve(); - }); - return serverless.variables - .populateService() - .should.be.fulfilled.then() - .finally(() => { - prepopulateServiceStub.restore(); - populateObjectStub.restore(); - }); - }); - it('should clear caches and remaining state *before* [pre]populating service', () => { - const prepopulateServiceStub = sinon - .stub(serverless.variables, 'prepopulateService') - .callsFake((val) => { - expect(serverless.variables.deep).to.eql([]); - expect(serverless.variables.tracker.getAll()).to.eql([]); - return BbPromise.resolve(val); - }); - const populateObjectStub = sinon - .stub(serverless.variables, 'populateObjectImpl') - .callsFake((val) => { - expect(serverless.variables.deep).to.eql([]); - expect(serverless.variables.tracker.getAll()).to.eql([]); - return BbPromise.resolve(val); - }); - serverless.variables.deep.push('${foo:}'); - const prms = BbPromise.resolve('foo'); - serverless.variables.tracker.add('foo:', prms, '${foo:}'); - prms.state = 'resolved'; - return serverless.variables - .populateService() - .should.be.fulfilled.then() - .finally(() => { - prepopulateServiceStub.restore(); - populateObjectStub.restore(); - }); - }); - it('should clear caches and remaining *after* [pre]populating service', () => { - const prepopulateServiceStub = sinon - .stub(serverless.variables, 'prepopulateService') - .callsFake((val) => { - serverless.variables.deep.push('${foo:}'); - const promise = BbPromise.resolve(val); - serverless.variables.tracker.add('foo:', promise, '${foo:}'); - promise.state = 'resolved'; - return BbPromise.resolve(); - }); - const populateObjectStub = sinon - .stub(serverless.variables, 'populateObjectImpl') - .callsFake((val) => { - serverless.variables.deep.push('${bar:}'); - const promise = BbPromise.resolve(val); - serverless.variables.tracker.add('bar:', promise, '${bar:}'); - promise.state = 'resolved'; - return BbPromise.resolve(); - }); - return serverless.variables - .populateService() - .should.be.fulfilled.then(() => { - expect(serverless.variables.deep).to.eql([]); - expect(serverless.variables.tracker.getAll()).to.eql([]); - }) - .finally(() => { - prepopulateServiceStub.restore(); - populateObjectStub.restore(); - }); - }); - }); - - describe('fallback', () => { - describe('should fallback if ${self} syntax fail to populate but fallback is provided', () => { - [ - { value: 'fallback123_*', description: 'regular ASCII characters' }, - { value: 'hello+world^*$@(!', description: 'different ASCII characters' }, - { value: '+++++', description: 'plus sign' }, - { value: 'システム管理者*', description: 'japanese characters' }, - { value: 'deす', description: 'mixed japanese ending' }, - { value: 'でsu', description: 'mixed japanese leading' }, - { value: 'suごi', description: 'mixed japanese middle' }, - { value: '①⑴⒈⒜Ⓐⓐⓟ ..▉가Ὠ', description: 'random unicode' }, - ].forEach((testCase) => { - it(testCase.description, () => { - serverless.variables.service.custom = { - settings: `\${self:nonExistent, "${testCase.value}"}`, - }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: testCase.value, - }); - }); - }); - }); - }); - - it('should fallback if ${opt} syntax fail to populate but fallback is provided', () => { - serverless.variables.service.custom = { - settings: '${opt:nonExistent, "fallback"}', - }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: 'fallback', - }); - }); - }); - - it('should fallback if ${env} syntax fail to populate but fallback is provided', () => { - serverless.variables.service.custom = { - settings: '${env:nonExistent, "fallback"}', - }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: 'fallback', - }); - }); - }); - - describe('file syntax', () => { - it('should fallback if file does not exist but fallback is provided', () => { - serverless.variables.service.custom = { - settings: '${file(~/config.yml):xyz, "fallback"}', - }; - - const fileExistsStub = sinon.stub(serverless.utils, 'fileExistsSync').returns(false); - const realpathSync = sinon.stub(fse, 'realpathSync').returns(`${os.homedir()}/config.yml`); - - return serverless.variables - .populateService() - .should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: 'fallback', - }); - }) - .finally(() => { - fileExistsStub.restore(); - realpathSync.restore(); - }); - }); - - it('should fallback if file exists but given key not found and fallback is provided', () => { - serverless.variables.service.custom = { - settings: '${file(~/config.yml):xyz, "fallback"}', - }; - - const fileExistsStub = sinon.stub(serverless.utils, 'fileExistsSync').returns(true); - const realpathSync = sinon.stub(fse, 'realpathSync').returns(`${os.homedir()}/config.yml`); - const readFileSyncStub = sinon.stub(serverless.utils, 'readFileSync').returns({ - test: 1, - test2: 'test2', - }); - - return serverless.variables - .populateService() - .should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: 'fallback', - }); - }) - .finally(() => { - fileExistsStub.restore(); - realpathSync.restore(); - readFileSyncStub.restore(); - }); - }); - }); - - describe('ensure unique instances', () => { - it('should not produce same instances for same variable patters used more than once', () => { - serverless.variables.service.custom = { - settings1: '${file(~/config.yml)}', - settings2: '${file(~/config.yml)}', - }; - - const fileExistsStub = sinon.stub(serverless.utils, 'fileExistsSync').returns(true); - const realpathSync = sinon.stub(fse, 'realpathSync').returns(`${os.homedir()}/config.yml`); - const readFileSyncStub = sinon.stub(serverless.utils, 'readFileSync').returns({ - test: 1, - test2: 'test2', - }); - - return serverless.variables - .populateService() - .should.be.fulfilled.then((result) => { - expect(result.custom.settings1).to.not.equal(result.custom.settings2); - }) - .finally(() => { - fileExistsStub.restore(); - realpathSync.restore(); - readFileSyncStub.restore(); - }); - }); - }); - - describe('aws-specific syntax', () => { - let awsProvider; - let requestStub; - beforeEach(() => { - awsProvider = new AwsProvider(serverless, {}); - requestStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.reject(new ServerlessError('Not found.', 400))); - }); - afterEach(() => { - requestStub.restore(); - }); - it('should fallback if ${s3} syntax fail to populate but fallback is provided', () => { - serverless.variables.service.custom = { - settings: '${s3:bucket/key, "fallback"}', - }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: 'fallback', - }); - }); - }); - - it('should fallback if ${cf} syntax fail to populate but fallback is provided', () => { - serverless.variables.service.custom = { - settings: '${cf:stack.value, "fallback"}', - }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: 'fallback', - }); - }); - }); - - it('should fallback if ${ssm} syntax fail to populate but fallback is provided', () => { - serverless.variables.service.custom = { - settings: '${ssm:/path/param, "fallback"}', - }; - return serverless.variables.populateService().should.be.fulfilled.then((result) => { - expect(result.custom).to.be.deep.eql({ - settings: 'fallback', - }); - }); - }); - - it('should throw an error if fallback fails too', () => { - serverless.variables.service.custom = { - settings: '${s3:bucket/key, ${ssm:/path/param}}', - }; - return serverless.variables.populateService().should.be.rejected; - }); - }); - }); - - describe('#prepopulateService', () => { - // TL;DR: call populateService to test prepopulateService (note addition of 'pre') - // - // The prepopulateService resolver basically assumes invocation of of populateService (i.e. that - // variable syntax is loaded, and that the service object is cleaned up. Just use - // populateService to do that work. - let awsProvider; - let populateObjectImplStub; - let requestStub; // just in case... don't want to actually call... - beforeEach(() => { - awsProvider = new AwsProvider(serverless, {}); - populateObjectImplStub = sinon.stub(serverless.variables, 'populateObjectImpl'); - populateObjectImplStub.withArgs(serverless.variables.service).returns(BbPromise.resolve()); - requestStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.reject(new Error('unexpected'))); - }); - afterEach(() => { - populateObjectImplStub.restore(); - requestStub.restore(); - }); - const prepopulatedProperties = [ - { name: 'region', getter: (provider) => provider.getRegion() }, - { name: 'stage', getter: (provider) => provider.getStage() }, - { name: 'profile', getter: (provider) => provider.getProfile() }, - ]; - describe('basic population tests', () => { - prepopulatedProperties.forEach((property) => { - it(`should populate variables in ${property.name} values`, () => { - _.set( - awsProvider.serverless.service.provider, - property.name, - '${self:foobar, "default"}' - ); - return serverless.variables - .populateService() - .should.be.fulfilled.then(() => - expect(property.getter(awsProvider)).to.be.eql('default') - ); - }); - }); - }); - // - describe('dependent service rejections', () => { - const dependentConfigs = [ - { value: '${cf:stack.value}', name: 'CloudFormation' }, - { value: '${s3:bucket/key}', name: 'S3' }, - { value: '${ssm:/path/param}', name: 'SSM' }, - ]; - prepopulatedProperties.forEach((property) => { - dependentConfigs.forEach((config) => { - it(`should reject ${config.name} variables in ${property.name} values`, () => { - _.set(awsProvider.serverless.service.provider, property.name, config.value); - return serverless.variables - .populateService() - .should.be.rejectedWith('Variable dependency failure'); - }); - it(`should reject recursively dependent ${config.name} service dependencies`, () => { - serverless.variables.service.custom = { - settings: config.value, - }; - awsProvider.serverless.service.provider.region = '${self:custom.settings.region}'; - return serverless.variables - .populateService() - .should.be.rejectedWith('Variable dependency failure'); - }); - }); - }); - }); - describe('dependent service non-interference', () => { - const stateCombinations = [ - { region: 'foo', state: 'bar' }, - { region: 'foo', state: '${self:bar, "bar"}' }, - { region: '${self:foo, "foo"}', state: 'bar' }, - { region: '${self:foo, "foo"}', state: '${self:bar, "bar"}' }, - ]; - stateCombinations.forEach((combination) => { - it('must leave the dependent services in their original state', () => { - const dependentMethods = [ - { name: 'getValueFromCf', original: serverless.variables.getValueFromCf }, - { name: 'getValueFromS3', original: serverless.variables.getValueFromS3 }, - { name: 'getValueFromSsm', original: serverless.variables.getValueFromSsm }, - ]; - awsProvider.serverless.service.provider.region = combination.region; - awsProvider.serverless.service.provider.state = combination.state; - return serverless.variables.populateService().should.be.fulfilled.then(() => { - dependentMethods.forEach((method) => { - expect(serverless.variables[method.name]).to.equal(method.original); - }); - }); - }); - }); - }); - }); - - describe('#getProperties', () => { - it('extracts all terminal properties of an object', () => { - const date = new Date(); - const regex = /^.*$/g; - const func = () => {}; - const obj = { - foo: { - bar: 'baz', - biz: 'buz', - }, - b: [{ c: 'd' }, { e: 'f' }], - g: date, - h: regex, - i: func, - }; - const expected = [ - { path: ['foo', 'bar'], value: 'baz' }, - { path: ['foo', 'biz'], value: 'buz' }, - { path: ['b', 0, 'c'], value: 'd' }, - { path: ['b', 1, 'e'], value: 'f' }, - { path: ['g'], value: date }, - { path: ['h'], value: regex }, - { path: ['i'], value: func }, - ]; - const result = serverless.variables.getProperties(obj, true, obj); - expect(result).to.eql(expected); - }); - it('ignores self references', () => { - const obj = {}; - obj.self = obj; - const expected = []; - const result = serverless.variables.getProperties(obj, true, obj); - expect(result).to.eql(expected); - }); - }); - - describe('#populateObject()', () => { - beforeEach(() => { - serverless.variables.loadVariableSyntax(); - }); - it('should populate object and return it', () => { - const object = { - stage: '${opt:stage}', - }; - const expectedPopulatedObject = { - stage: 'prod', - }; - - sinon.stub(serverless.variables, 'populateValue').resolves('prod'); - - return serverless.variables - .populateObject(object) - .then((populatedObject) => { - expect(populatedObject).to.deep.equal(expectedPopulatedObject); - }) - .finally(() => serverless.variables.populateValue.restore()); - }); - - it('should persist keys with dot notation', () => { - const object = { - stage: '${opt:stage}', - }; - object['some.nested.key'] = 'hello'; - const expectedPopulatedObject = { - stage: 'prod', - }; - expectedPopulatedObject['some.nested.key'] = 'hello'; - const populateValueStub = sinon.stub(serverless.variables, 'populateValue').callsFake( - // eslint-disable-next-line no-template-curly-in-string - (val) => { - return val === '${opt:stage}' ? BbPromise.resolve('prod') : BbPromise.resolve(val); - } - ); - return serverless.variables - .populateObject(object) - .should.become(expectedPopulatedObject) - .then() - .finally(() => populateValueStub.restore()); - }); - describe('significant variable usage corner cases', () => { - let service; - const makeDefault = () => ({ - service: 'my-service', - provider: { - name: 'aws', - }, - }); - beforeEach(() => { - service = makeDefault(); - // eslint-disable-next-line no-template-curly-in-string - service.provider.variableSyntax = '\\${([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*?]+?)}'; // default - serverless.service = serverless.variables.service = service; - serverless.variables.loadVariableSyntax(); - delete service.provider.variableSyntax; - }); - it('should properly replace self-references', () => { - service.custom = { - me: '${self:}', - }; - const expected = makeDefault(); - expected.custom = { - me: expected, - }; - return expect( - serverless.variables.populateObject(service).then((result) => { - expect(jc.stringify(result)).to.eql(jc.stringify(expected)); - }) - ).to.be.fulfilled; - }); - it('should properly populate embedded variables', () => { - service.custom = { - val0: 'my value 0', - val1: '0', // eslint-disable-next-line no-template-curly-in-string - val2: '${self:custom.val${self:custom.val1}}', - }; - const expected = { - val0: 'my value 0', - val1: '0', - val2: 'my value 0', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should properly populate an overwrite with a default value that is a string', () => { - service.custom = { - val0: 'my value', // eslint-disable-next-line no-template-curly-in-string - val1: '${self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2, "string"}', - }; - const expected = { - val0: 'my value', - val1: 'string', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should properly populate an overwrite with a default value that is the string *', () => { - service.custom = { - val0: 'my value', // eslint-disable-next-line no-template-curly-in-string - val1: '${self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2, "*"}', - }; - const expected = { - val0: 'my value', - val1: '*', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should properly populate an overwrite with a default value that is a string w/*', () => { - service.custom = { - val0: 'my value', // eslint-disable-next-line no-template-curly-in-string - val1: '${self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2, "foo*"}', - }; - const expected = { - val0: 'my value', - val1: 'foo*', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should properly populate overwrites where the first value is valid', () => { - service.custom = { - val0: 'my value', // eslint-disable-next-line no-template-curly-in-string - val1: '${self:custom.val0, self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2}', - }; - const expected = { - val0: 'my value', - val1: 'my value', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it("should treat '*' literally when not wrapped in quotes", async () => { - service.custom = { - val0: "${self:custom.*, 'fallback'}", - }; - const expected = { val0: 'fallback' }; - const result = await serverless.variables.populateObject(service.custom); - expect(result).to.eql(expected); - }); - it('should properly populate overwrites where the middle value is valid', () => { - service.custom = { - val0: 'my value', // eslint-disable-next-line no-template-curly-in-string - val1: '${self:custom.NOT_A_VAL1, self:custom.val0, self:custom.NOT_A_VAL2}', - }; - const expected = { - val0: 'my value', - val1: 'my value', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should properly populate overwrites where the last value is valid', () => { - service.custom = { - val0: 'my value', // eslint-disable-next-line no-template-curly-in-string - val1: '${self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2, self:custom.val0}', - }; - const expected = { - val0: 'my value', - val1: 'my value', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should properly populate overwrites with nested variables in the first value', () => { - service.custom = { - val0: 'my value', - val1: 0, // eslint-disable-next-line no-template-curly-in-string - val2: '${self:custom.val${self:custom.val1}, self:custom.NO_1, self:custom.NO_2}', - }; - const expected = { - val0: 'my value', - val1: 0, - val2: 'my value', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should properly populate overwrites with nested variables in the middle value', () => { - service.custom = { - val0: 'my value', - val1: 0, // eslint-disable-next-line no-template-curly-in-string - val2: '${self:custom.NO_1, self:custom.val${self:custom.val1}, self:custom.NO_2}', - }; - const expected = { - val0: 'my value', - val1: 0, - val2: 'my value', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should properly populate overwrites with nested variables in the last value', () => { - service.custom = { - val0: 'my value', - val1: 0, // eslint-disable-next-line no-template-curly-in-string - val2: '${self:custom.NO_1, self:custom.NO_2, self:custom.val${self:custom.val1}}', - }; - const expected = { - val0: 'my value', - val1: 0, - val2: 'my value', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should properly replace duplicate variable declarations', () => { - service.custom = { - val0: 'my value', - val1: '${self:custom.val0}', - val2: '${self:custom.val0}', - }; - const expected = { - val0: 'my value', - val1: 'my value', - val2: 'my value', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - it('should recursively populate, regardless of order and duplication', () => { - service.custom = { - val1: '${self:custom.depVal}', - depVal: '${self:custom.val0}', - val0: 'my value', - val2: '${self:custom.depVal}', - }; - const expected = { - val1: 'my value', - depVal: 'my value', - val0: 'my value', - val2: 'my value', - }; - return expect( - serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - }) - ).to.be.fulfilled; - }); - // see https://github.com/serverless/serverless/pull/4713#issuecomment-366975172 - it('should handle deep references into deep variables', () => { - service.provider.stage = 'dev'; - service.custom = { - stage: '${env:stage, self:provider.stage}', - secrets: '${self:custom.${self:custom.stage}}', - dev: { - SECRET: 'secret', - }, - environment: { - SECRET: '${self:custom.secrets.SECRET}', - }, - }; - const expected = { - stage: 'dev', - secrets: { - SECRET: 'secret', - }, - dev: { - SECRET: 'secret', - }, - environment: { - SECRET: 'secret', - }, - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle deep variables that reference overrides', () => { - service.custom = { - val1: '${self:not.a.value, "bar"}', - val2: '${self:custom.val1}', - }; - const expected = { - val1: 'bar', - val2: 'bar', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle deep references into deep variables', () => { - service.custom = { - val0: { - foo: 'bar', - }, - val1: '${self:custom.val0}', - val2: '${self:custom.val1.foo}', - }; - const expected = { - val0: { - foo: 'bar', - }, - val1: { - foo: 'bar', - }, - val2: 'bar', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle deep variables that reference overrides', () => { - service.custom = { - val1: '${self:not.a.value, "bar"}', - val2: 'foo${self:custom.val1}', - }; - const expected = { - val1: 'bar', - val2: 'foobar', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle referenced deep variables that reference overrides', () => { - service.custom = { - val1: '${self:not.a.value, "bar"}', - val2: '${self:custom.val1}', - val3: '${self:custom.val2}', - }; - const expected = { - val1: 'bar', - val2: 'bar', - val3: 'bar', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle partial referenced deep variables that reference overrides', () => { - service.custom = { - val1: '${self:not.a.value, "bar"}', - val2: '${self:custom.val1}', - val3: 'foo${self:custom.val2}', - }; - const expected = { - val1: 'bar', - val2: 'bar', - val3: 'foobar', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle referenced contained deep variables that reference overrides', () => { - service.custom = { - val1: '${self:not.a.value, "bar"}', - val2: 'foo${self:custom.val1}', - val3: '${self:custom.val2}', - }; - const expected = { - val1: 'bar', - val2: 'foobar', - val3: 'foobar', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle multiple referenced contained deep variables referencing overrides', () => { - service.custom = { - val0: '${self:not.a.value, "foo"}', - val1: '${self:not.a.value, "bar"}', - val2: '${self:custom.val0}:${self:custom.val1}', - val3: '${self:custom.val2}', - }; - const expected = { - val0: 'foo', - val1: 'bar', - val2: 'foo:bar', - val3: 'foo:bar', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle overrides that are populated by unresolvable deep variables', () => { - service.custom = { - val0: 'foo', - val1: '${self:custom.val0}', - val2: '${self:custom.val1.notAnAttribute, "fallback"}', - }; - const expected = { - val0: 'foo', - val1: 'foo', - val2: 'fallback', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle embedded deep variable replacements in overrides', () => { - service.custom = { - foo: 'bar', - val0: 'foo', - val1: '${self:custom.val0, "fallback 1"}', - val2: '${self:custom.${self:custom.val0, self:custom.val1}, "fallback 2"}', - }; - const expected = { - foo: 'bar', - val0: 'foo', - val1: 'foo', - val2: 'bar', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should deal with overwites that reference embedded deep references', () => { - service.custom = { - val0: 'val', - val1: 'val0', - val2: '${self:custom.val1}', - val3: '${self:custom.${self:custom.val2}, "fallback"}', - val4: '${self:custom.val3, self:custom.val3}', - }; - const expected = { - val0: 'val', - val1: 'val0', - val2: 'val0', - val3: 'val', - val4: 'val', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should preserve whitespace in double-quote literal fallback', () => { - service.custom = { - val0: '${self:custom.val, "rate(3 hours)"}', - }; - const expected = { - val0: 'rate(3 hours)', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should preserve whitespace in single-quote literal fallback', () => { - service.custom = { - val0: "${self:custom.val, 'rate(1 hour)'}", - }; - const expected = { - val0: 'rate(1 hour)', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should preserve question mark in single-quote literal fallback', () => { - service.custom = { - val0: "${self:custom.val, '?'}", - }; - const expected = { - val0: '?', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should preserve question mark in single-quote literal fallback', () => { - service.custom = { - val0: "${self:custom.val, 'cron(0 0 * * ? *)'}", - }; - const expected = { - val0: 'cron(0 0 * * ? *)', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should accept whitespace in variables', () => { - service.custom = { - val0: '${self: custom.val}', - val: 'foobar', - }; - const expected = { - val: 'foobar', - val0: 'foobar', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle deep variables regardless of custom variableSyntax', () => { - service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*?]+?)}}'; - serverless.variables.loadVariableSyntax(); - delete service.provider.variableSyntax; - service.custom = { - my0thStage: 'DEV', - my1stStage: '${{self:custom.my0thStage}}', - my2ndStage: '${{self:custom.my1stStage}}', - }; - const expected = { - my0thStage: 'DEV', - my1stStage: 'DEV', - my2ndStage: 'DEV', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle deep variable continuations regardless of custom variableSyntax', () => { - service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*?]+?)}}'; - serverless.variables.loadVariableSyntax(); - delete service.provider.variableSyntax; - service.custom = { - my0thStage: { we: 'DEV' }, - my1stStage: '${{self:custom.my0thStage}}', - my2ndStage: '${{self:custom.my1stStage.we}}', - }; - const expected = { - my0thStage: { we: 'DEV' }, - my1stStage: { we: 'DEV' }, - my2ndStage: 'DEV', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle deep variables regardless of recursion into custom variableSyntax', () => { - service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*?]+?)}}'; - serverless.variables.loadVariableSyntax(); - delete service.provider.variableSyntax; - service.custom = { - my0thIndex: '0th', - my1stIndex: '1st', - my0thStage: 'DEV', - my1stStage: '${{self:custom.my${{self:custom.my0thIndex}}Stage}}', - my2ndStage: '${{self:custom.my${{self:custom.my1stIndex}}Stage}}', - }; - const expected = { - my0thIndex: '0th', - my1stIndex: '1st', - my0thStage: 'DEV', - my1stStage: 'DEV', - my2ndStage: 'DEV', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should handle deep variables in complex recursions of custom variableSyntax', () => { - service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\\\'",\\-\\/\\(\\)*?]+?)}}'; - serverless.variables.loadVariableSyntax(); - delete service.provider.variableSyntax; - service.custom = { - i0: '0', - s0: 'DEV', - s1: '${{self:custom.s0}}! ${{self:custom.s0}}', - s2: 'I am a ${{self:custom.s0}}! A ${{self:custom.s${{self:custom.i0}}}}!', - s3: '${{self:custom.s0}}!, I am a ${{self:custom.s1}}!, ${{self:custom.s2}}', - }; - const expected = { - i0: '0', - s0: 'DEV', - s1: 'DEV! DEV', - s2: 'I am a DEV! A DEV!', - s3: 'DEV!, I am a DEV! DEV!, I am a DEV! A DEV!', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - describe('file reading cases', () => { - let tmpDirPath; - beforeEach(() => { - tmpDirPath = getTmpDirPath(); - fse.mkdirsSync(tmpDirPath); - serverless.serviceDir = tmpDirPath; - }); - afterEach(() => { - fse.removeSync(tmpDirPath); - }); - const makeTempFile = (fileName, fileContent) => { - fse.outputFileSync(path.join(tmpDirPath, fileName), fileContent); - }; - const asyncFileName = 'async.load.js'; - const asyncContent = `'use strict'; -let i = 0 -const str = () => new Promise((resolve) => { - setTimeout(() => { - i += 1 // side effect - resolve(\`my-async-value-\${i}\`) - }, 200); -}); -const obj = () => new Promise((resolve) => { - setTimeout(() => { - i += 1 // side effect - resolve({ - val0: \`my-async-value-\${i}\`, - val1: '\${opt:stage}', - }); - }, 200); -}); -module.exports = { - str, - obj, -}; -`; - it('should populate any given variable only once', () => { - makeTempFile(asyncFileName, asyncContent); - service.custom = { - val1: '${self:custom.val0}', - val2: '${self:custom.val1}', - val0: `\${file(${asyncFileName}):str}`, - }; - const expected = { - val1: 'my-async-value-1', - val2: 'my-async-value-1', - val0: 'my-async-value-1', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should still work with a default file name in double or single quotes', () => { - makeTempFile(asyncFileName, asyncContent); - service.custom = { - val1: '${self:custom.val0}', - val2: '${self:custom.val1}', - val3: `\${file(\${self:custom.nonexistent, "${asyncFileName}"}):str}`, - val0: `\${file(\${self:custom.nonexistent, '${asyncFileName}'}):str}`, - }; - const expected = { - val1: 'my-async-value-1', - val2: 'my-async-value-1', - val3: 'my-async-value-1', - val0: 'my-async-value-1', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should populate any given variable only once regardless of ordering or reference count', () => { - makeTempFile(asyncFileName, asyncContent); - service.custom = { - val9: '${self:custom.val7}', - val7: '${self:custom.val5}', - val5: '${self:custom.val3}', - val3: '${self:custom.val1}', - val1: '${self:custom.val0}', - val2: '${self:custom.val1}', - val4: '${self:custom.val3}', - val6: '${self:custom.val5}', - val8: '${self:custom.val7}', - val0: `\${file(${asyncFileName}):str}`, - }; - const expected = { - val9: 'my-async-value-1', - val7: 'my-async-value-1', - val5: 'my-async-value-1', - val3: 'my-async-value-1', - val1: 'my-async-value-1', - val2: 'my-async-value-1', - val4: 'my-async-value-1', - val6: 'my-async-value-1', - val8: 'my-async-value-1', - val0: 'my-async-value-1', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it('should populate async objects with contained variables', () => { - makeTempFile(asyncFileName, asyncContent); - serverless.variables.options = { - stage: 'dev', - }; - service.custom = { - obj: `\${file(${asyncFileName}):obj}`, - }; - const expected = { - obj: { - val0: 'my-async-value-1', - val1: 'dev', - }, - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - it("should populate variables from filesnames including '@', e.g scoped npm packages", () => { - const fileName = `./node_modules/@scoped-org/${asyncFileName}`; - makeTempFile(fileName, asyncContent); - service.custom = { - val0: `\${file(${fileName}):str}`, - }; - const expected = { - val0: 'my-async-value-1', - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - const selfFileName = 'self.yml'; - const selfContent = `foo: baz -bar: \${self:custom.self.foo} -`; - it('should populate a "cyclic" reference across an unresolved dependency (issue #4687)', () => { - makeTempFile(selfFileName, selfContent); - service.custom = { - self: `\${file(${selfFileName})}`, - }; - const expected = { - self: { - foo: 'baz', - bar: 'baz', - }, - }; - return serverless.variables.populateObject(service.custom).should.become(expected); - }); - const emptyFileName = 'empty.js'; - const emptyContent = `'use strict'; -module.exports = { - func: () => ({ value: 'a value' }), -} -`; - it('should reject population of an attribute not exported from a file', () => { - makeTempFile(emptyFileName, emptyContent); - service.custom = { - val: `\${file(${emptyFileName}):func.notAValue}`, - }; - return serverless.variables - .populateObject(service.custom) - .should.be.rejectedWith( - ServerlessError, - 'Invalid variable syntax when referencing file' - ); - }); - }); - }); - }); - - describe('#populateProperty()', () => { - beforeEach(() => { - serverless.variables.loadVariableSyntax(); - }); - - it('should call overwrite if overwrite syntax provided', () => { - // eslint-disable-next-line no-template-curly-in-string - const property = 'my stage is ${opt:stage, self:provider.stage}'; - serverless.variables.options = { stage: 'dev' }; - serverless.service.provider.stage = 'prod'; - return serverless.variables - .populateProperty(property) - .should.eventually.eql('my stage is dev'); - }); - - it('should allow a single-quoted string if overwrite syntax provided', () => { - // eslint-disable-next-line no-template-curly-in-string - const property = "my stage is ${opt:stage, 'prod'}"; - serverless.variables.options = {}; - return serverless.variables - .populateProperty(property) - .should.eventually.eql('my stage is prod'); - }); - - it('should allow a double-quoted string if overwrite syntax provided', () => { - // eslint-disable-next-line no-template-curly-in-string - const property = 'my stage is ${opt:stage, "prod"}'; - serverless.variables.options = {}; - return serverless.variables - .populateProperty(property) - .should.eventually.eql('my stage is prod'); - }); - - it('should not allow partially double-quoted string', async () => { - const property = '${opt:stage, prefix"prod"suffix}'; - serverless.variables.options = {}; - const handleUnresolvedSpy = sinon.spy(serverless.variables, 'handleUnresolved'); - try { - expect(await serverless.variables.populateProperty(property)).to.equal(undefined); - expect(handleUnresolvedSpy.callCount).to.equal(1); - } finally { - handleUnresolvedSpy.restore(); - } - }); - - it('should allow a boolean with value true if overwrite syntax provided', () => { - const property = '${opt:stage, true}'; - serverless.variables.options = {}; - return serverless.variables.populateProperty(property).should.eventually.eql(true); - }); - - it('should allow a boolean with value false if overwrite syntax provided', () => { - const property = '${opt:stage, false}'; - serverless.variables.options = {}; - return serverless.variables.populateProperty(property).should.eventually.eql(false); - }); - - it('should not match a boolean with value containing word true or false if overwrite syntax provided', () => { - const property = '${opt:stage, foofalsebar}'; - serverless.variables.options = {}; - const handleUnresolvedSpy = sinon.spy(serverless.variables, 'handleUnresolved'); - return serverless.variables - .populateProperty(property) - .should.become(undefined) - .then(() => { - expect(handleUnresolvedSpy.callCount).to.equal(1); - }) - .finally(() => { - handleUnresolvedSpy.restore(); - }); - }); - - it('should allow an integer if overwrite syntax provided', () => { - const property = '${opt:quantity, 123}'; - serverless.variables.options = {}; - return serverless.variables.populateProperty(property).should.eventually.eql(123); - }); - - it('should call getValueFromSource if no overwrite syntax provided', () => { - // eslint-disable-next-line no-template-curly-in-string - const property = 'my stage is ${opt:stage}'; - serverless.variables.options = { stage: 'prod' }; - return serverless.variables - .populateProperty(property) - .should.eventually.eql('my stage is prod'); - }); - - it('should warn if an SSM parameter does not exist', () => { - const options = { - stage: 'prod', - region: 'us-east-1', - }; - serverless.variables.options = options; - const awsProvider = new AwsProvider(serverless, options); - const param = '/some/path/to/invalidparam'; - const property = `\${ssm:${param}}`; - const error = Object.assign(new Error(`Parameter ${param} not found.`), { - providerError: { statusCode: 400 }, - }); - const requestStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.reject(error)); - const handleUnresolvedSpy = sinon.spy(serverless.variables, 'handleUnresolved'); - - return serverless.variables - .populateProperty(property) - .should.become(undefined) - .then(() => { - expect(requestStub.callCount).to.equal(1); - expect(handleUnresolvedSpy.callCount).to.equal(1); - }) - .finally(() => { - requestStub.restore(); - handleUnresolvedSpy.restore(); - }); - }); - - it('should throw an Error if the SSM request fails', () => { - const options = { - stage: 'prod', - region: 'us-east-1', - }; - serverless.variables.options = options; - const awsProvider = new AwsProvider(serverless, options); - const param = '/some/path/to/invalidparam'; - const property = `\${ssm:${param}}`; - const error = new ServerlessError('Some random failure.', 123); - const requestStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.reject(error)); - return serverless.variables - .populateProperty(property) - .should.be.rejectedWith(ServerlessError) - .then(() => expect(requestStub.callCount).to.equal(1)) - .finally(() => requestStub.restore()); - }); - - it('should run recursively if nested variables provided', () => { - // eslint-disable-next-line no-template-curly-in-string - const property = 'my stage is ${env:${opt:name}}'; - process.env.TEST_VAR = 'dev'; - serverless.variables.options = { name: 'TEST_VAR' }; - return serverless.variables - .populateProperty(property) - .should.eventually.eql('my stage is dev'); - }); - - it('should run recursively through many nested variables', () => { - // eslint-disable-next-line no-template-curly-in-string - const property = 'my stage is ${env:${opt:name}}'; - process.env.TEST_VAR = 'dev'; - serverless.variables.options = { - name: 'T${opt:lvl0}', - lvl0: 'E${opt:lvl1}', - lvl1: 'S${opt:lvl2}', - lvl2: 'T${opt:lvl3}', - lvl3: '_${opt:lvl4}', - lvl4: 'V${opt:lvl5}', - lvl5: 'A${opt:lvl6}', - lvl6: 'R', - }; - return serverless.variables - .populateProperty(property) - .should.eventually.eql('my stage is dev'); - }); - }); - - describe('#populateVariable()', () => { - it('should populate string variables as sub string', () => { - const valueToPopulate = 'dev'; - const matchedString = '${opt:stage}'; - // eslint-disable-next-line no-template-curly-in-string - const property = 'my stage is ${opt:stage}'; - serverless.variables - .populateVariable(property, matchedString, valueToPopulate) - .should.eql('my stage is dev'); - }); - - it('should populate number variables as sub string', () => { - const valueToPopulate = 5; - const matchedString = '${opt:number}'; - // eslint-disable-next-line no-template-curly-in-string - const property = 'your account number is ${opt:number}'; - serverless.variables - .populateVariable(property, matchedString, valueToPopulate) - .should.eql('your account number is 5'); - }); - - it('should populate non string variables', () => { - const valueToPopulate = 5; - const matchedString = '${opt:number}'; - const property = '${opt:number}'; - return serverless.variables - .populateVariable(property, matchedString, valueToPopulate) - .should.equal(5); - }); - - it('should throw error if populating non string or non number variable as sub string', () => { - const valueToPopulate = {}; - const matchedString = '${opt:object}'; - // eslint-disable-next-line no-template-curly-in-string - const property = 'your account number is ${opt:object}'; - return expect(() => - serverless.variables.populateVariable(property, matchedString, valueToPopulate) - ).to.throw(ServerlessError); - }); - }); - - describe('#splitByComma', () => { - it('should return a given empty string', () => { - const input = ''; - const expected = [input]; - expect(serverless.variables.splitByComma(input)).to.eql(expected); - }); - it('should return a undelimited string', () => { - const input = 'foo:bar'; - const expected = [input]; - expect(serverless.variables.splitByComma(input)).to.eql(expected); - }); - it('should split basic comma delimited strings', () => { - const input = 'my,values,to,split'; - const expected = ['my', 'values', 'to', 'split']; - expect(serverless.variables.splitByComma(input)).to.eql(expected); - }); - it('should remove leading and following white space', () => { - const input = ' \t\nfoobar\n\t '; - const expected = ['foobar']; - expect(serverless.variables.splitByComma(input)).to.eql(expected); - }); - it('should remove white space surrounding commas', () => { - const input = 'a,b ,c , d, e , f\t,g\n,h,\ti,\nj,\t\n , \n\tk'; - const expected = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']; - expect(serverless.variables.splitByComma(input)).to.eql(expected); - }); - it('should ignore quoted commas', () => { - const input = '",", \',\', ",\', \',\'", "\',\', \',\'", \',", ","\', \'",", ","\''; - const expected = ['","', "','", "\",', ','\"", "\"',', ','\"", '\',", ","\'', '\'",", ","\'']; - expect(serverless.variables.splitByComma(input)).to.eql(expected); - }); - it('should deal with a combination of these cases', () => { - const input = " \t\n'a'\t\n , \n\t\"foo,bar\", opt:foo, \",\", ',', \"',', ','\", foo\n\t "; - const expected = ["'a'", '"foo,bar"', 'opt:foo', '","', "','", "\"',', ','\"", 'foo']; - expect(serverless.variables.splitByComma(input)).to.eql(expected); - }); - }); - - describe('#overwrite()', () => { - beforeEach(() => { - serverless.service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*?]+?)}}'; - serverless.variables.loadVariableSyntax(); - delete serverless.service.provider.variableSyntax; - }); - it('should overwrite undefined and null values', () => { - const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - getValueFromSourceStub.onCall(0).resolves(undefined); - getValueFromSourceStub.onCall(1).resolves(null); - getValueFromSourceStub.onCall(2).resolves('variableValue'); - return serverless.variables - .overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal('variableValue'); - expect(getValueFromSourceStub).to.have.been.calledThrice; - }) - .finally(() => getValueFromSourceStub.restore()); - }); - - it('should overwrite empty object values', () => { - const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - getValueFromSourceStub.onCall(0).resolves({}); - getValueFromSourceStub.onCall(1).resolves('variableValue'); - return serverless.variables - .overwrite(['opt:stage', 'env:stage']) - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal('variableValue'); - expect(getValueFromSourceStub).to.have.been.calledTwice; - }) - .finally(() => getValueFromSourceStub.restore()); - }); - - it('should overwrite values with an array even if it is empty', () => { - const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - getValueFromSourceStub.onCall(0).resolves(undefined); - getValueFromSourceStub.onCall(1).resolves([]); - return serverless.variables - .overwrite(['opt:stage', 'env:stage']) - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.deep.equal([]); - expect(getValueFromSourceStub).to.have.been.calledTwice; - }) - .finally(() => getValueFromSourceStub.restore()); - }); - - it('should not overwrite 0 values', () => { - const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - getValueFromSourceStub.onCall(0).resolves(0); - getValueFromSourceStub.onCall(1).resolves('variableValue'); - return serverless.variables - .overwrite(['opt:stage', 'env:stage']) - .should.become(0) - .then() - .finally(() => getValueFromSourceStub.restore()); - }); - - it('should not overwrite false values', () => { - const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - getValueFromSourceStub.onCall(0).resolves(false); - getValueFromSourceStub.onCall(1).resolves('variableValue'); - return serverless.variables - .overwrite(['opt:stage', 'env:stage']) - .should.become(false) - .then() - .finally(() => getValueFromSourceStub.restore()); - }); - - it('should skip getting values once a value has been found', () => { - const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - getValueFromSourceStub.onCall(0).resolves(undefined); - getValueFromSourceStub.onCall(1).resolves('variableValue'); - getValueFromSourceStub.onCall(2).resolves('variableValue2'); - return serverless.variables - .overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) - .should.be.fulfilled.then((valueToPopulate) => - expect(valueToPopulate).to.equal('variableValue') - ) - .finally(() => getValueFromSourceStub.restore()); - }); - it('should properly handle string values containing commas', () => { - const str = '"foo,bar"'; - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource') - .resolves(undefined); - return serverless.variables - .overwrite(['opt:stage', str]) - .should.be.fulfilled.then(() => - expect(getValueFromSourceStub.getCall(1).args[0]).to.eql(str) - ) - .finally(() => getValueFromSourceStub.restore()); - }); - }); - - describe('#getValueFromSource()', () => { - const variableValue = 'variableValue'; - let getValueFromSlsStub; - let getValueFromEnvStub; - let getValueFromOptionsStub; - let getValueFromSelfStub; - let getValueFromFileStub; - let getValueFromCfStub; - let getValueFromS3Stub; - let getValueFromSsmStub; - - beforeEach(() => { - getValueFromSlsStub = sinon - .stub(serverless.variables.variableResolvers[0], 'resolver') - .resolves('variableValue'); - getValueFromEnvStub = sinon - .stub(serverless.variables.variableResolvers[1], 'resolver') - .resolves('variableValue'); - getValueFromOptionsStub = sinon - .stub(serverless.variables.variableResolvers[2], 'resolver') - .resolves('variableValue'); - getValueFromSelfStub = sinon - .stub(serverless.variables.variableResolvers[3], 'resolver') - .resolves('variableValue'); - getValueFromFileStub = sinon - .stub(serverless.variables.variableResolvers[4], 'resolver') - .resolves('variableValue'); - getValueFromCfStub = sinon - .stub(serverless.variables.variableResolvers[5], 'resolver') - .resolves('variableValue'); - getValueFromS3Stub = sinon - .stub(serverless.variables.variableResolvers[6], 'resolver') - .resolves('variableValue'); - getValueFromSsmStub = sinon - .stub(serverless.variables.variableResolvers[10], 'resolver') - .resolves('variableValue'); - }); - - afterEach(() => { - serverless.variables.variableResolvers[0].resolver.restore(); - serverless.variables.variableResolvers[1].resolver.restore(); - serverless.variables.variableResolvers[2].resolver.restore(); - serverless.variables.variableResolvers[3].resolver.restore(); - serverless.variables.variableResolvers[4].resolver.restore(); - serverless.variables.variableResolvers[5].resolver.restore(); - serverless.variables.variableResolvers[6].resolver.restore(); - serverless.variables.variableResolvers[10].resolver.restore(); - }); - - it('should call getValueFromSls if referencing sls var', () => - serverless.variables - .getValueFromSource('sls:instanceId') - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal(variableValue); - expect(getValueFromSlsStub).to.have.been.called; - expect(getValueFromSlsStub).to.have.been.calledWith('sls:instanceId'); - })); - - it('should call getValueFromEnv if referencing env var', () => - serverless.variables - .getValueFromSource('env:TEST_VAR') - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal(variableValue); - expect(getValueFromEnvStub).to.have.been.called; - expect(getValueFromEnvStub).to.have.been.calledWith('env:TEST_VAR'); - })); - - it('should call getValueFromOptions if referencing an option', () => - serverless.variables - .getValueFromSource('opt:stage') - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal(variableValue); - expect(getValueFromOptionsStub).to.have.been.called; - expect(getValueFromOptionsStub).to.have.been.calledWith('opt:stage'); - })); - - it('should call getValueFromSelf if referencing from self', () => - serverless.variables - .getValueFromSource('self:provider') - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal(variableValue); - expect(getValueFromSelfStub).to.have.been.called; - expect(getValueFromSelfStub).to.have.been.calledWith('self:provider'); - })); - - it('should call getValueFromFile if referencing from another file', () => - serverless.variables - .getValueFromSource('file(./config.yml)') - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal(variableValue); - expect(getValueFromFileStub).to.have.been.called; - expect(getValueFromFileStub).to.have.been.calledWith('file(./config.yml)'); - })); - - it('should call getValueFromCf if referencing CloudFormation Outputs', () => - serverless.variables - .getValueFromSource('cf:test-stack.testOutput') - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal(variableValue); - expect(getValueFromCfStub).to.have.been.called; - expect(getValueFromCfStub).to.have.been.calledWith('cf:test-stack.testOutput'); - })); - - it('should call getValueFromS3 if referencing variable in S3', () => - serverless.variables - .getValueFromSource('s3:test-bucket/path/to/key') - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal(variableValue); - expect(getValueFromS3Stub).to.have.been.called; - expect(getValueFromS3Stub).to.have.been.calledWith('s3:test-bucket/path/to/key'); - })); - - it('should call getValueFromSsm if referencing variable in SSM', () => - serverless.variables - .getValueFromSource('ssm:/test/path/to/param') - .should.be.fulfilled.then((valueToPopulate) => { - expect(valueToPopulate).to.equal(variableValue); - expect(getValueFromSsmStub).to.have.been.called; - expect(getValueFromSsmStub).to.have.been.calledWith('ssm:/test/path/to/param'); - })); - - it('should reject invalid sources', () => - serverless.variables - .getValueFromSource('weird:source') - .should.be.rejectedWith(ServerlessError)); - - describe('caching', () => { - const sources = [ - { functionIndex: 0, function: 'getValueFromSls', variableString: 'sls:instanceId' }, - { functionIndex: 1, function: 'getValueFromEnv', variableString: 'env:NODE_ENV' }, - { functionIndex: 2, function: 'getValueFromOptions', variableString: 'opt:stage' }, - { functionIndex: 3, function: 'getValueFromSelf', variableString: 'self:provider' }, - { functionIndex: 4, function: 'getValueFromFile', variableString: 'file(./config.yml)' }, - { - functionIndex: 5, - function: 'getValueFromCf', - variableString: 'cf:test-stack.testOutput', - }, - { - functionIndex: 6, - function: 'getValueFromS3', - variableString: 's3:test-bucket/path/to/ke', - }, - { - functionIndex: 10, - function: 'getValueFromSsm', - variableString: 'ssm:/test/path/to/param', - }, - ]; - sources.forEach((source) => { - it(`should only call ${source.function} once, returning the cached value otherwise`, () => { - const getValueFunctionStub = - serverless.variables.variableResolvers[source.functionIndex].resolver; - return BbPromise.all([ - serverless.variables - .getValueFromSource(source.variableString) - .should.become(variableValue), - BbPromise.delay(100).then(() => - serverless.variables - .getValueFromSource(source.variableString) - .should.become(variableValue) - ), - ]).then(() => { - expect(getValueFunctionStub).to.have.been.calledOnce; - expect(getValueFunctionStub).to.have.been.calledWith(source.variableString); - }); - }); - }); - }); - }); - - describe('#getValueFromSls()', () => { - it('should get variable from Serverless Framework provided variables', () => { - serverless.instanceId = 12345678; - return serverless.variables.getValueFromSls('sls:instanceId').then((valueToPopulate) => { - expect(valueToPopulate).to.equal(12345678); - }); - }); - }); - - describe('#getValueFromEnv()', () => { - it('should get variable from environment variables', () => { - process.env.TEST_VAR = 'someValue'; - return serverless.variables.getValueFromEnv('env:TEST_VAR').should.become('someValue'); - }); - - it('should allow top-level references to the environment variables hive', () => { - process.env.TEST_VAR = 'someValue'; - return serverless.variables.getValueFromEnv('env:').then((valueToPopulate) => { - expect(valueToPopulate.TEST_VAR).to.be.equal('someValue'); - }); - }); - }); - - describe('#getValueFromOptions()', () => { - it('should get variable from options', () => { - serverless.variables.options = { stage: 'prod' }; - return serverless.variables.getValueFromOptions('opt:stage').should.become('prod'); - }); - - it('should allow top-level references to the options hive', () => { - serverless.variables.options = { stage: 'prod' }; - return serverless.variables - .getValueFromOptions('opt:') - .should.become(serverless.variables.options); - }); - }); - - describe('#getValueFromSelf()', () => { - beforeEach(() => { - serverless.service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._@\'",\\-\\/\\(\\)*?]+?)}}'; - serverless.variables.loadVariableSyntax(); - delete serverless.service.provider.variableSyntax; - }); - it('should get variable from self serverless.yml file', () => { - serverless.variables.service = { - service: 'testService', - provider: serverless.service.provider, - }; - return serverless.variables.getValueFromSelf('self:service').should.become('testService'); - }); - it('should redirect ${self:service.name} to ${self:service}', () => { - serverless.variables.service = { - service: 'testService', - provider: serverless.service.provider, - }; - return serverless.variables - .getValueFromSelf('self:service.name') - .should.become('testService'); - }); - it('should redirect ${self:provider} to ${self:provider.name}', () => { - serverless.variables.service = { - service: 'testService', - provider: { name: 'aws' }, - }; - return serverless.variables.getValueFromSelf('self:provider').should.become('aws'); - }); - it('should redirect ${self:service.awsKmsKeyArn} to ${self:serviceObject.awsKmsKeyArn}', () => { - const keyArn = 'arn:aws:kms:us-east-1:xxxxxxxxxxxx:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; - serverless.variables.service = { - service: 'testService', - serviceObject: { - name: 'testService', - awsKmsKeyArn: keyArn, - }, - }; - return serverless.variables - .getValueFromSelf('self:service.awsKmsKeyArn') - .should.become(keyArn); - }); - it('should handle self-references to the root of the serverless.yml file', () => { - serverless.variables.service = { - service: 'testService', - provider: 'testProvider', - defaults: serverless.service.defaults, - }; - return serverless.variables - .getValueFromSelf('self:') - .should.eventually.equal(serverless.variables.service); - }); - }); - - describe('#getValueFromFile()', () => { - it('should work for absolute paths with ~ ', () => { - const expectedFileName = `${os.homedir()}/somedir/config.yml`; - const configYml = { - test: 1, - test2: 'test2', - testObj: { - sub: 2, - prob: 'prob', - }, - }; - const fileExistsStub = sinon.stub(serverless.utils, 'fileExistsSync').returns(true); - const realpathSync = sinon.stub(fse, 'realpathSync').returns(expectedFileName); - const readFileSyncStub = sinon.stub(serverless.utils, 'readFileSync').returns(configYml); - return serverless.variables - .getValueFromFile('file(~/somedir/config.yml)') - .should.be.fulfilled.then((valueToPopulate) => { - expect(realpathSync).to.not.have.been.called; - expect(fileExistsStub).to.have.been.calledWithMatch(expectedFileName); - expect(readFileSyncStub).to.have.been.calledWithMatch(expectedFileName); - expect(valueToPopulate).to.deep.equal(configYml); - }) - .finally(() => { - realpathSync.restore(); - readFileSyncStub.restore(); - fileExistsStub.restore(); - }); - }); - - it('should populate an entire variable file', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const configYml = { - test: 1, - test2: 'test2', - testObj: { - sub: 2, - prob: 'prob', - }, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), yaml.dump(configYml)); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./config.yml)') - .should.eventually.eql(configYml); - }); - - it('should get undefined if non existing file and the second argument is true', () => { - const tmpDirPath = getTmpDirPath(); - serverless.serviceDir = tmpDirPath; - const realpathSync = sinon.spy(fse, 'realpathSync'); - const existsSync = sinon.spy(fse, 'existsSync'); - return serverless.variables - .getValueFromFile('file(./non-existing.yml)') - .should.be.fulfilled.then((valueToPopulate) => { - expect(realpathSync).to.not.have.been.called; - expect(existsSync).to.have.been.calledOnce; - expect(valueToPopulate).to.be.undefined; - }) - .finally(() => { - realpathSync.restore(); - existsSync.restore(); - }); - }); - - it('should populate non json/yml files', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world'); - serverless.serviceDir = tmpDirPath; - return serverless.variables.getValueFromFile('file(./someFile)').should.become('hello world'); - }); - - it('should populate symlinks', function () { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const realFilePath = path.join(tmpDirPath, 'someFile'); - const symlinkPath = path.join(tmpDirPath, 'refSomeFile'); - SUtils.writeFileSync(realFilePath, 'hello world'); - try { - fse.ensureSymlinkSync(realFilePath, symlinkPath); - } catch (error) { - skipOnDisabledSymlinksInWindows(error, this, afterCallback); - throw error; - } - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./refSomeFile)') - .should.become('hello world') - .then() - .finally(() => { - fse.removeSync(realFilePath); - fse.removeSync(symlinkPath); - }); - }); - - it('should trim trailing whitespace and new line character', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world \n'); - serverless.serviceDir = tmpDirPath; - return serverless.variables.getValueFromFile('file(./someFile)').should.become('hello world'); - }); - - it('should populate from another file when variable is of any type', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const configYml = { - test0: 0, - test1: 'test1', - test2: { - sub: 2, - prob: 'prob', - }, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), yaml.dump(configYml)); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./config.yml):test2.sub') - .should.become(configYml.test2.sub); - }); - - it('should populate from a javascript file that exports a function', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = 'module.exports.hello=function(){return "hello world";};'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./hello.js):hello') - .should.become('hello world'); - }); - - it('should populate from a javascript file that exports a string', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = 'module.exports.hello="hello world";'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./hello.js):hello') - .should.become('hello world'); - }); - - it('should populate an entire variable exported by a javascript file function', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = 'module.exports=function(){return { hello: "hello world" };};'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./hello.js)') - .should.become({ hello: 'hello world' }); - }); - - it('should populate an entire variable exported by a javascript file object', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = 'module.exports={ hello: "hello world" };'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./hello.js)') - .should.become({ hello: 'hello world' }); - }); - - it('should populate an entire variable exported by a javascript file literal', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = 'module.exports="hello world";'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - return serverless.variables.getValueFromFile('file(./hello.js)').should.become('hello world'); - }); - - it('should populate deep object from a javascript file', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = `module.exports.hello=function(){ - return {one:{two:{three: 'hello world'}}} - };`; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - serverless.variables.loadVariableSyntax(); - return serverless.variables - .getValueFromFile('file(./hello.js):hello.one.two.three') - .should.become('hello world'); - }); - - it('should preserve the exported function context when executing', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = ` - module.exports.one = {two: {three: 'hello world'}} - module.exports.hello=function(){ return this; };`; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - serverless.variables.loadVariableSyntax(); - return serverless.variables - .getValueFromFile('file(./hello.js):hello.one.two.three') - .should.become('hello world'); - }); - - it('should file variable not using ":" syntax', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const configYml = { - test: 1, - test2: 'test2', - testObj: { - sub: 2, - prob: 'prob', - }, - }; - SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), yaml.dump(configYml)); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./config.yml).testObj.sub') - .should.be.rejectedWith(ServerlessError); - }); - - it('should throw an error if resolved value is undefined', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = 'module.exports=undefined;'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./hello.js)') - .should.be.rejectedWith(ServerlessError); - }); - - it('should throw an error if resolved value is a symbol', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = 'module.exports=Symbol()'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./hello.js)') - .should.be.rejectedWith(ServerlessError); - }); - - it('should throw an error if resolved value is a function', () => { - const SUtils = new Utils(); - const tmpDirPath = getTmpDirPath(); - const jsData = 'module.exports=function(){ return function(){}; };'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.serviceDir = tmpDirPath; - return serverless.variables - .getValueFromFile('file(./hello.js)') - .should.be.rejectedWith(ServerlessError); - }); - }); - - describe('#getValueFromCf()', () => { - it('should get variable from CloudFormation', () => { - const options = { - stage: 'prod', - region: 'us-west-2', - }; - const awsProvider = new AwsProvider(serverless, options); - serverless.setProvider('aws', awsProvider); - serverless.variables.options = options; - const awsResponseMock = { - Stacks: [ - { - Outputs: [ - { - OutputKey: 'MockExport', - OutputValue: 'MockValue', - }, - ], - }, - ], - }; - const cfStub = sinon - .stub(serverless.getProvider('aws'), 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromCf('cf:some-stack.MockExport') - .should.become('MockValue') - .then(() => { - expect(cfStub).to.have.been.calledOnce; - expect(cfStub).to.have.been.calledWithExactly( - 'CloudFormation', - 'describeStacks', - { StackName: 'some-stack' }, - { useCache: true } - ); - }) - .finally(() => cfStub.restore()); - }); - - it('should get variable from CloudFormation of different region', () => { - const options = { - stage: 'prod', - region: 'us-west-2', - }; - const awsProvider = new AwsProvider(serverless, options); - serverless.setProvider('aws', awsProvider); - serverless.variables.options = options; - const awsResponseMock = { - Stacks: [ - { - Outputs: [ - { - OutputKey: 'MockExport', - OutputValue: 'MockValue', - }, - ], - }, - ], - }; - const cfStub = sinon - .stub(serverless.getProvider('aws'), 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromCf('cf.us-east-1:some-stack.MockExport') - .should.become('MockValue') - .then(() => { - expect(cfStub).to.have.been.calledOnce; - expect(cfStub).to.have.been.calledWithExactly( - 'CloudFormation', - 'describeStacks', - { StackName: 'some-stack' }, - { region: 'us-east-1', useCache: true } - ); - }) - .finally(() => cfStub.restore()); - }); - - it('should reject CloudFormation variables that do not exist', () => { - const options = { - stage: 'prod', - region: 'us-west-2', - }; - const awsProvider = new AwsProvider(serverless, options); - serverless.setProvider('aws', awsProvider); - serverless.variables.options = options; - const awsResponseMock = { - Stacks: [ - { - Outputs: [ - { - OutputKey: 'MockExport', - OutputValue: 'MockValue', - }, - ], - }, - ], - }; - const cfStub = sinon - .stub(serverless.getProvider('aws'), 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromCf('cf:some-stack.DoestNotExist') - .should.be.rejectedWith( - ServerlessError, - /to request a non exported variable from CloudFormation/ - ) - .then(() => { - expect(cfStub).to.have.been.calledOnce; - expect(cfStub).to.have.been.calledWithExactly( - 'CloudFormation', - 'describeStacks', - { StackName: 'some-stack' }, - { useCache: true } - ); - }) - .finally(() => cfStub.restore()); - }); - }); - - describe('#getValueFromS3()', () => { - let awsProvider; - beforeEach(() => { - const options = { - stage: 'prod', - region: 'us-west-2', - }; - awsProvider = new AwsProvider(serverless, options); - serverless.setProvider('aws', awsProvider); - serverless.variables.options = options; - }); - it('should get variable from S3', () => { - const awsResponseMock = { - Body: 'MockValue', - }; - const s3Stub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromS3('s3:some.bucket/path/to/key') - .should.become('MockValue') - .then(() => { - expect(s3Stub).to.have.been.calledOnce; - expect(s3Stub).to.have.been.calledWithExactly( - 'S3', - 'getObject', - { - Bucket: 'some.bucket', - Key: 'path/to/key', - }, - { useCache: true } - ); - }) - .finally(() => s3Stub.restore()); - }); - - it('should throw error if error getting value from S3', () => { - const error = new Error('The specified bucket is not valid'); - const requestStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.reject(error)); - return expect(serverless.variables.getValueFromS3('s3:some.bucket/path/to/key')) - .to.be.rejectedWith( - ServerlessError, - 'Error getting value for s3:some.bucket/path/to/key. The specified bucket is not valid' - ) - .then() - .finally(() => requestStub.restore()); - }); - }); - - describe('#getValueFromSsm()', () => { - const param = 'Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - let awsProvider; - beforeEach(() => { - const options = { - stage: 'prod', - region: 'us-west-2', - }; - awsProvider = new AwsProvider(serverless, options); - serverless.setProvider('aws', awsProvider); - serverless.variables.options = options; - }); - it('should get variable from Ssm using regular-style param', () => { - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromSsm(`ssm:${param}`) - .should.become(value) - .then(() => { - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }) - .finally(() => ssmStub.restore()); - }); - it('should get variable from Ssm of different region', () => { - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromSsm(`ssm.us-east-1:${param}`) - .should.become(value) - .then(() => { - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { region: 'us-east-1', useCache: true } - ); - }) - .finally(() => ssmStub.restore()); - }); - it('should get variable from Ssm using path-style param', () => { - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromSsm(`ssm:${param}`) - .should.become(value) - .then(() => { - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }) - .finally(() => ssmStub.restore()); - }); - it('should get encrypted variable from Ssm using extended syntax', () => { - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromSsm(`ssm:${param}~true`) - .should.become(value) - .then(() => { - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: true, - }, - { useCache: true } - ); - }) - .finally(() => ssmStub.restore()); - }); - it('should get unencrypted variable from Ssm using extended syntax', () => { - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromSsm(`ssm:${param}~false`) - .should.become(value) - .then(() => { - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }) - .finally(() => ssmStub.restore()); - }); - it('should ignore bad values for extended syntax', () => { - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponseMock)); - return serverless.variables - .getValueFromSsm(`ssm:${param}~badvalue`) - .should.become(value) - .then(() => { - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }) - .finally(() => ssmStub.restore()); - }); - it('should get split StringList variable from Ssm using extended syntax', () => { - const stringListValue = 'MockValue1,MockValue2'; - const parsedValue = ['MockValue1', 'MockValue2']; - const stringListResponseMock = { - Parameter: { - Value: stringListValue, - Type: 'StringList', - }, - }; - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(stringListResponseMock)); - return serverless.variables - .getValueFromSsm(`ssm:${param}~split`) - .should.become(parsedValue) - .then(() => { - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }) - .finally(() => ssmStub.restore()); - }); - it('should get unsplit StringList variable from Ssm by default', () => { - const stringListValue = 'MockValue1,MockValue2'; - const stringListResponseMock = { - Parameter: { - Value: stringListValue, - Type: 'StringList', - }, - }; - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(stringListResponseMock)); - return serverless.variables - .getValueFromSsm(`ssm:${param}`) - .should.become(stringListValue) - .then(() => { - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }) - .finally(() => ssmStub.restore()); - }); - it('should warn when attempting to split a non-StringList Ssm variable', () => { - const logWarningSpy = sinon.spy(slsError, 'logWarning'); - const consoleLogStub = sinon.stub(console, 'log').returns(); - const ProxyQuiredVariables = proxyquire('../../../../lib/classes/Variables.js', { - './Error': logWarningSpy, - }); - const varProxy = new ProxyQuiredVariables(serverless); - const stringListResponseMock = { - Parameter: { - Value: value, - Type: 'String', - }, - }; - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(stringListResponseMock)); - return varProxy - .getValueFromSsm(`ssm:${param}~split`) - .should.become(value) - .then(() => { - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - expect(logWarningSpy).to.have.been.calledWithExactly( - `Cannot split SSM parameter '${param}' of type 'String'. Must be 'StringList'.` - ); - }) - .finally(() => { - ssmStub.restore(); - logWarningSpy.restore(); - consoleLogStub.restore(); - }); - }); - describe('Referencing to AWS SecretsManager', () => { - it('should NOT parse value as json if not referencing to AWS SecretsManager', () => { - const secretParam = '/path/to/foo-bar'; - const jsonLikeText = '{"str":"abc","num":123}'; - const awsResponse = { - Parameter: { - Value: jsonLikeText, - }, - }; - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponse)); - return serverless.variables - .getValueFromSsm(`ssm:${secretParam}~true`) - .should.become(jsonLikeText) - .then() - .finally(() => ssmStub.restore()); - }); - it('should parse value as json if returned value is json-like', () => { - const secretParam = '/aws/reference/secretsmanager/foo-bar'; - const jsonLikeText = '{"str":"abc","num":123}'; - const json = { - str: 'abc', - num: 123, - }; - const awsResponse = { - Parameter: { - Value: jsonLikeText, - }, - }; - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponse)); - return serverless.variables - .getValueFromSsm(`ssm:${secretParam}~true`) - .should.become(json) - .then() - .finally(() => ssmStub.restore()); - }); - it('should get value as text if returned value is NOT json-like', () => { - const secretParam = '/aws/reference/secretsmanager/foo-bar'; - const plainText = 'I am plain text'; - const awsResponse = { - Parameter: { - Value: plainText, - }, - }; - const ssmStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.resolve(awsResponse)); - return serverless.variables - .getValueFromSsm(`ssm:${secretParam}~true`) - .should.become(plainText) - .then() - .finally(() => ssmStub.restore()); - }); - }); - it('should return undefined if SSM parameter does not exist', () => { - const error = Object.assign(new Error(`Parameter ${param} not found.`), { - providerError: { statusCode: 400 }, - }); - const requestStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.reject(error)); - return serverless.variables - .getValueFromSsm(`ssm:${param}`) - .should.become(undefined) - .then() - .finally(() => requestStub.restore()); - }); - - it('should reject if SSM request returns unexpected error', () => { - const error = new Error( - 'User: is not authorized to perform: ssm:GetParameter on resource: ' - ); - const requestStub = sinon - .stub(awsProvider, 'request') - .callsFake(() => BbPromise.reject(error)); - return serverless.variables - .getValueFromSsm(`ssm:${param}`) - .should.be.rejected.then() - .finally(() => requestStub.restore()); - }); - }); - - describe('#getValueStrToBool()', () => { - const errMessage = 'Unexpected strToBool input; expected either "true", "false", "0", or "1".'; - beforeEach(() => { - serverless.variables.service = { - service: 'testService', - provider: serverless.service.provider, - }; - serverless.variables.loadVariableSyntax(); - }); - it('regex for "true" input', () => { - expect(serverless.variables.strToBoolRefSyntax.test('${strToBool(true)}')).to.equal(true); - }); - it('regex for "false" input', () => { - expect(serverless.variables.strToBoolRefSyntax.test('${strToBool(false)}')).to.equal(true); - }); - it('regex for "0" input', () => { - expect(serverless.variables.strToBoolRefSyntax.test('${strToBool(0)}')).to.equal(true); - }); - it('regex for "1" input', () => { - expect(serverless.variables.strToBoolRefSyntax.test('${strToBool(1)}')).to.equal(true); - }); - it('regex for "null" input', () => { - expect(serverless.variables.strToBoolRefSyntax.test('${strToBool(null)}')).to.equal(true); - }); - it('regex for truthy input', () => { - expect(serverless.variables.strToBoolRefSyntax.test('${strToBool(anything)}')).to.equal(true); - }); - it('regex for empty input', () => { - expect(serverless.variables.strToBoolRefSyntax.test('${strToBool()}')).to.equal(false); - }); - it('true (string) should return true (boolean)', () => { - return serverless.variables.getValueStrToBool('strToBool(true)').should.become(true); - }); - it('false (string) should return false (boolean)', () => { - return serverless.variables.getValueStrToBool('strToBool(false)').should.become(false); - }); - it('1 (string) should return true (boolean)', () => { - return serverless.variables.getValueStrToBool('strToBool(1)').should.become(true); - }); - it('0 (string) should return false (boolean)', () => { - return serverless.variables.getValueStrToBool('strToBool(0)').should.become(false); - }); - it('truthy string should throw an error', () => { - return serverless.variables - .getValueStrToBool('strToBool(anything)') - .catch((err) => err.message) - .should.become(errMessage); - }); - it('null (string) should throw an error', () => { - return serverless.variables - .getValueStrToBool('strToBool(null)') - .catch((err) => err.message) - .should.become(errMessage); - }); - it('strToBool(true) as an input to strToBool', () => { - const input = serverless.variables.getValueStrToBool('strToBool(true)'); - return serverless.variables.getValueStrToBool(input).should.become(true); - }); - it('strToBool(false) as an input to strToBool', () => { - const input = serverless.variables.getValueStrToBool('strToBool(false)'); - return serverless.variables.getValueStrToBool(input).should.become(true); - }); - }); - - describe('#getDeeperValue()', () => { - it('should get deep values', () => { - const valueToPopulateMock = { - service: 'testService', - custom: { - subProperty: { - deep: 'deepValue', - }, - }, - }; - serverless.variables.loadVariableSyntax(); - return serverless.variables - .getDeeperValue(['custom', 'subProperty', 'deep'], valueToPopulateMock) - .should.become('deepValue'); - }); - it('should not throw error if referencing invalid properties', () => { - const valueToPopulateMock = { - service: 'testService', - custom: { - subProperty: 'hello', - }, - }; - serverless.variables.loadVariableSyntax(); - return serverless.variables - .getDeeperValue(['custom', 'subProperty', 'deep', 'deeper'], valueToPopulateMock) - .should.eventually.deep.equal({}); - }); - it('should return a simple deep variable when final deep value is variable', () => { - serverless.variables.service = { - service: 'testService', - custom: { - subProperty: { - // eslint-disable-next-line no-template-curly-in-string - deep: '${self:custom.anotherVar.veryDeep}', - }, - }, - provider: serverless.service.provider, - }; - serverless.variables.loadVariableSyntax(); - return serverless.variables - .getDeeperValue(['custom', 'subProperty', 'deep'], serverless.variables.service) - .should.become('${deep:0}'); - }); - it('should return a deep continuation when middle deep value is variable', () => { - serverless.variables.service = { - service: 'testService', - custom: { - anotherVar: '${self:custom.var}', - }, - provider: serverless.service.provider, - }; - serverless.variables.loadVariableSyntax(); - return serverless.variables - .getDeeperValue(['custom', 'anotherVar', 'veryDeep'], serverless.variables.service) - .should.become('${deep:0.veryDeep}'); - }); - }); - - describe('#handleUnresolved()', () => { - let logWarningSpy; - let consoleLogStub; - let varProxy; - - beforeEach(() => { - logWarningSpy = sinon.spy(slsError, 'logWarning'); - consoleLogStub = sinon.stub(console, 'log').returns(); - const ProxyQuiredVariables = proxyquire('../../../../lib/classes/Variables.js', { - './Error': logWarningSpy, - }); - varProxy = new ProxyQuiredVariables(serverless); - }); - - afterEach(() => { - logWarningSpy.restore(); - consoleLogStub.restore(); - }); - - it('should do nothing if variable has valid value.', () => { - varProxy.handleUnresolved('self:service', 'a-valid-value'); - expect(logWarningSpy).to.not.have.been.calledOnce; - }); - - describe('when variable string does not match any of syntax', () => { - // These situation happen when deep variable population fails - it('should do nothing if variable has null value.', () => { - varProxy.handleUnresolved('', null); - expect(logWarningSpy).to.not.have.been.calledOnce; - }); - - it('should do nothing if variable has undefined value.', () => { - varProxy.handleUnresolved('', undefined); - expect(logWarningSpy).to.not.have.been.calledOnce; - }); - - it('should do nothing if variable has empty object value.', () => { - varProxy.handleUnresolved('', {}); - expect(logWarningSpy).to.not.have.been.calledOnce; - }); - }); - it('should log if variable has null value.', () => { - varProxy.handleUnresolved('self:service', null); - expect(logWarningSpy).to.have.been.calledOnce; - }); - it('should log if variable has undefined value.', () => { - varProxy.handleUnresolved('self:service', undefined); - expect(logWarningSpy).to.have.been.calledOnce; - }); - - it('should log if variable has empty object value.', () => { - varProxy.handleUnresolved('self:service', {}); - expect(logWarningSpy).to.have.been.calledOnce; - }); - - it('should not log if variable has empty array value.', () => { - varProxy.handleUnresolved('self:service', []); - expect(logWarningSpy).to.not.have.been.called; - }); - - it('should detect the "environment variable" variable type', () => { - varProxy.handleUnresolved('env:service', null); - expect(logWarningSpy).to.have.been.calledOnce; - expect(logWarningSpy.args[0][0]).to.contain('environment variable'); - }); - - it('should detect the "option" variable type', () => { - varProxy.handleUnresolved('opt:service', null); - expect(logWarningSpy).to.have.been.calledOnce; - expect(logWarningSpy.args[0][0]).to.contain('option'); - }); - - it('should detect the "service attribute" variable type', () => { - varProxy.handleUnresolved('self:service', null); - expect(logWarningSpy).to.have.been.calledOnce; - expect(logWarningSpy.args[0][0]).to.contain('service attribute'); - }); - - it('should detect the "file" variable type', () => { - varProxy.handleUnresolved('file(service)', null); - expect(logWarningSpy).to.have.been.calledOnce; - expect(logWarningSpy.args[0][0]).to.contain('file'); - }); - }); -}); - -describe('test/unit/lib/classes/Variables.test.js', () => { - let processedConfig = null; - before(async () => { - const result = await runServerless({ - fixture: 'variables-legacy', - command: 'print', - shouldUseLegacyVariablesResolver: true, - }); - processedConfig = result.serverless.service; - }); - - it('should support ${file(...)} syntax', () => { - expect(processedConfig.custom.importedFile).to.deep.equal({ - foo: 'bar', - }); - }); - - it('should support ${file(...):key} syntax', () => { - expect(processedConfig.custom.importedFileWithKey).to.equal('bar'); - }); - - it('should support ${file(...)} syntax for Terraform state', () => { - expect(processedConfig.custom.importedTerraformState).to.deep.equal({ - version: 4, - terraform_version: '0.14.4', - serial: 11, - lineage: '12ab3c45-abc1-0a1b-1a23-a12b34567c89', - outputs: { - listenerarn: { - value: - 'arn:aws:elasticloadbalancing:us-west-2:123456789876:listener/app/myapp/1a2b3c4f1a23456b/a1b23c45de6789fa', - type: 'string', - }, - }, - resources: [], - }); - }); - - it('should support ${file(...):key} syntax for Terraform state', () => { - expect(processedConfig.custom.importedTerraformStateWithKey).to.equal('string'); - }); - - it('should ignore native CloudFormation variables', () => { - expect(processedConfig.custom.awsVariable).to.equal('${AWS::Region}'); - }); - - it('should ignore CloudFormation references', () => { - expect(processedConfig.custom.cloudFormationReference).to.equal('${AnotherResource}'); - }); - - it('should support ${self:key} syntax', () => { - expect(processedConfig.custom.selfReference).to.equal('bar'); - }); - it('should support ${self:} syntax', () => { - expect(processedConfig.custom.serviceReference).to.equal(processedConfig); - }); - it('should support nested resolution', () => { - expect(processedConfig.custom.nestedReference).to.equal('resolvedNested'); - }); - - it('should handle resolving variables when `prototype` is part of the path', async () => { - expect(processedConfig.custom.prototype.nestedInPrototype).to.equal('bar-in-prototype'); - }); - - describe('variable resolving', () => { - describe('when unresolvedVariablesNotificationMode is set to "error"', () => { - it('should error for missing "environment variable" type variables', async () => { - await expect( - runServerless({ - fixture: 'variables-legacy', - command: 'print', - configExt: { - unresolvedVariablesNotificationMode: 'error', - custom: { myVariable: '${env:missingEnvVar}' }, - }, - shouldUseLegacyVariablesResolver: true, - }) - ).to.eventually.be.rejected.and.have.property('code', 'UNRESOLVED_CONFIG_VARIABLE'); - }); - - it('should error for missing "option" type variables', async () => { - await expect( - runServerless({ - fixture: 'variables-legacy', - command: 'print', - configExt: { - unresolvedVariablesNotificationMode: 'error', - custom: { myVariable: '${opt:missingOpt}' }, - }, - shouldUseLegacyVariablesResolver: true, - }) - ).to.eventually.be.rejected.and.have.property('code', 'UNRESOLVED_CONFIG_VARIABLE'); - }); - - it('should error for missing "service attribute" type variables', async () => { - await expect( - runServerless({ - fixture: 'variables-legacy', - command: 'print', - configExt: { - unresolvedVariablesNotificationMode: 'error', - custom: { myVariable: '${self:missingAttribute}' }, - }, - shouldUseLegacyVariablesResolver: true, - }) - ).to.eventually.be.rejected.and.have.property('code', 'UNRESOLVED_CONFIG_VARIABLE'); - }); - - it('should error for missing "file" type variables', async () => { - await expect( - runServerless({ - fixture: 'variables-legacy', - command: 'print', - configExt: { - unresolvedVariablesNotificationMode: 'error', - custom: { myVariable: '${file(./missingFile)}' }, - }, - shouldUseLegacyVariablesResolver: true, - }) - ).to.eventually.be.rejected.and.have.property('code', 'UNRESOLVED_CONFIG_VARIABLE'); - }); - }); - - describe('when unresolvedVariablesNotificationMode is set to "warn"', () => { - it('should warn', async () => { - const { stdoutData } = await runServerless({ - fixture: 'variables-legacy', - command: 'print', - configExt: { - unresolvedVariablesNotificationMode: 'warn', - custom: { - myVariable1: '${env:missingEnvVar}', - myVariable2: '${opt:missingOpt}', - myVariable3: '${self:missingAttribute}', - myVariable4: '${file(./missingFile)}', - }, - }, - shouldUseLegacyVariablesResolver: true, - }); - expect(stdoutData).to.include('Serverless Warning'); - expect(stdoutData).to.include('A valid environment variable to satisfy the declaration'); - expect(stdoutData).to.include('A valid option to satisfy the declaration'); - expect(stdoutData).to.include('A valid service attribute to satisfy the declaration'); - expect(stdoutData).to.include('A valid file to satisfy the declaration'); - }); - }); - - describe('when unresolvedVariablesNotificationMode is not set', () => { - it('should warn', async () => { - const { stdoutData } = await runServerless({ - fixture: 'variables-legacy', - command: 'print', - configExt: { - custom: { - myVariable1: '${env:missingEnvVar}', - myVariable2: '${opt:missingOpt}', - myVariable3: '${self:missingAttribute}', - myVariable4: '${file(./missingFile)}', - }, - }, - shouldUseLegacyVariablesResolver: true, - }); - - expect(stdoutData).to.include('Serverless Warning'); - expect(stdoutData).to.include('A valid environment variable to satisfy the declaration'); - expect(stdoutData).to.include('A valid option to satisfy the declaration'); - expect(stdoutData).to.include('A valid service attribute to satisfy the declaration'); - expect(stdoutData).to.include('A valid file to satisfy the declaration'); - }); - }); - }); -}); diff --git a/test/unit/lib/cli/handle-error.test.js b/test/unit/lib/cli/handle-error.test.js index bcab06b80..029cdfe17 100644 --- a/test/unit/lib/cli/handle-error.test.js +++ b/test/unit/lib/cli/handle-error.test.js @@ -3,8 +3,7 @@ const chai = require('chai'); const sinon = require('sinon'); -const path = require('path'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); +const observeOutput = require('@serverless/test/observe-output'); const handleError = require('../../../../lib/cli/handle-error'); const isStandaloneExecutable = require('../../../../lib/utils/isStandaloneExecutable'); const ServerlessError = require('../../../../lib/serverless-error'); @@ -15,31 +14,19 @@ chai.use(require('sinon-chai')); const expect = chai.expect; describe('test/unit/lib/cli/handle-error.test.js', () => { - it('should log environment information', async () => { - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - () => handleError(new ServerlessError('Test error')) - ); - expect(stdoutData).to.have.string('Serverless Error'); - expect(stdoutData).to.have.string('Test error'); - expect(stdoutData).to.have.string('Your Environment Information'); - expect(stdoutData).to.have.string('Operating System:'); - expect(stdoutData).to.have.string('Node Version:'); - expect(stdoutData).to.have.string('Framework Version:'); - expect(stdoutData).to.have.string('Plugin Version:'); - expect(stdoutData).to.have.string('SDK Version:'); - expect(stdoutData).to.have.string('Components Version:'); + it('should output environment information', async () => { + const output = await observeOutput(() => handleError(new ServerlessError('Test error'))); + expect(output).to.have.string('Environment: '); + expect(output).to.have.string('node'); + expect(output).to.have.string('framework'); + expect(output).to.have.string('plugin'); + expect(output).to.have.string('SDK'); }); it('should support `isUncaughtException` option', async () => { const processExitStub = sinon.stub(process, 'exit').returns(); try { - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - () => handleError(new ServerlessError('Test error'), { isUncaughtException: true }) - ); + await handleError(new ServerlessError('Test error'), { isUncaughtException: true }); expect(processExitStub.called).to.be.true; } finally { processExitStub.restore(); @@ -48,57 +35,25 @@ describe('test/unit/lib/cli/handle-error.test.js', () => { if (isStandaloneExecutable) { it('should report standalone installation', async () => { - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - () => handleError(new ServerlessError('Test error')) - ); - expect(stdoutData).to.have.string('(standalone)'); + const output = await observeOutput(() => handleError(new ServerlessError('Test error'))); + expect(output).to.have.string('(standalone)'); }); } else { it('should support `isLocallyInstalled` option', async () => { - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - () => handleError(new ServerlessError('Test error'), { isLocallyInstalled: false }) + const output = await observeOutput(() => + handleError(new ServerlessError('Test error'), { isLocallyInstalled: false }) ); - expect(stdoutData).to.not.have.string('(local)'); - stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - () => handleError(new ServerlessError('Test error'), { isLocallyInstalled: true }) + expect(output).to.not.have.string('(local)'); + const output2 = await observeOutput(() => + handleError(new ServerlessError('Test error'), { isLocallyInstalled: true }) ); - expect(stdoutData).to.have.string('(local)'); + expect(output2).to.have.string('(local)'); }); } - it('should print stack trace with SLS_DEBUG', async () => { - let stdoutData = ''; - process.env.SLS_DEBUG = '1'; - await overrideStdoutWrite( - (data) => (stdoutData += data), - () => handleError(new ServerlessError('Test error')) - ); - expect(stdoutData).to.have.string(path.basename(__filename)); - }); - - it('should not print stack trace without SLS_DEBUG', async () => { - let stdoutData = ''; - delete process.env.SLS_DEBUG; - await overrideStdoutWrite( - (data) => (stdoutData += data), - () => handleError(new ServerlessError('Test error')) - ); - expect(stdoutData).to.not.have.string(path.basename(__filename)); - }); - it('should handle non-error objects', async () => { - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - () => handleError('NON-ERROR') - ); - expect(stdoutData).to.have.string('NON-ERROR'); + const output = await observeOutput(() => handleError(handleError('NON-ERROR'))); + expect(output).to.have.string('NON-ERROR'); }); describe('with mocked telemetry', () => { @@ -122,14 +77,9 @@ describe('test/unit/lib/cli/handle-error.test.js', () => { }); it('should record telemetry only if `hasTelemetryBeenReported` is `false`', async () => { - // Override to avoid printing to stdout in tests - await overrideStdoutWrite( - () => {}, - () => - handleErrorWithMocks(new ServerlessError('Test error', 'ERR_CODE'), { - hasTelemetryBeenReported: false, - }) - ); + await handleErrorWithMocks(new ServerlessError('Test error', 'ERR_CODE'), { + hasTelemetryBeenReported: false, + }); expect(generateTelemetryPayloadStub).to.be.calledOnce; expect(storeTelemetryLocallyStub).to.be.calledOnce; expect(sendTelemetryStub).to.be.calledOnce; @@ -143,14 +93,9 @@ describe('test/unit/lib/cli/handle-error.test.js', () => { }); it('should add `location` to `failureReason` in telemetry if error code missing', async () => { - // Override to avoid printing to stdout in tests - await overrideStdoutWrite( - () => {}, - () => - handleErrorWithMocks(new ServerlessError('Test error'), { - hasTelemetryBeenReported: false, - }) - ); + await handleErrorWithMocks(new ServerlessError('Test error'), { + hasTelemetryBeenReported: false, + }); expect(generateTelemetryPayloadStub).to.be.calledOnce; expect(storeTelemetryLocallyStub).to.be.calledOnce; expect(sendTelemetryStub).to.be.calledOnce; @@ -160,14 +105,9 @@ describe('test/unit/lib/cli/handle-error.test.js', () => { }); it('should add `location` to `failureReason` in telemetry for non-user errors', async () => { - // Override to avoid printing to stdout in tests - await overrideStdoutWrite( - () => {}, - () => - handleErrorWithMocks(new Error('Test error'), { - hasTelemetryBeenReported: false, - }) - ); + await handleErrorWithMocks(new Error('Test error'), { + hasTelemetryBeenReported: false, + }); expect(generateTelemetryPayloadStub).to.be.calledOnce; expect(storeTelemetryLocallyStub).to.be.calledOnce; expect(sendTelemetryStub).to.be.calledOnce; @@ -177,25 +117,16 @@ describe('test/unit/lib/cli/handle-error.test.js', () => { }); it('should not record telemetry if `hasTelemetryBeenReported` is `true`', async () => { - // Override to avoid printing to stdout in tests - await overrideStdoutWrite( - () => {}, - () => - handleErrorWithMocks(new ServerlessError('Test error'), { - hasTelemetryBeenReported: true, - }) - ); + await handleErrorWithMocks(new ServerlessError('Test error'), { + hasTelemetryBeenReported: true, + }); expect(generateTelemetryPayloadStub).not.to.be.called; expect(storeTelemetryLocallyStub).not.to.be.called; expect(sendTelemetryStub).not.to.be.called; }); it('should not record telemetry if `hasTelemetryBeenReported` is not passed', async () => { - // Override to avoid printing to stdout in tests - await overrideStdoutWrite( - () => {}, - () => handleErrorWithMocks(new ServerlessError('Test error')) - ); + await handleErrorWithMocks(new ServerlessError('Test error')); expect(generateTelemetryPayloadStub).not.to.be.called; expect(storeTelemetryLocallyStub).not.to.be.called; expect(sendTelemetryStub).not.to.be.called; @@ -205,14 +136,9 @@ describe('test/unit/lib/cli/handle-error.test.js', () => { // Ensure that `commandSchema` is not included in result of `resolveInput` resolveInputStub.returns({}); - // Override to avoid printing to stdout in tests - await overrideStdoutWrite( - () => {}, - () => - handleErrorWithMocks(new ServerlessError('Test error'), { - hasTelemetryBeenReported: false, - }) - ); + await handleErrorWithMocks(new ServerlessError('Test error'), { + hasTelemetryBeenReported: false, + }); expect(generateTelemetryPayloadStub).not.to.be.called; expect(storeTelemetryLocallyStub).not.to.be.called; expect(sendTelemetryStub).not.to.be.called; diff --git a/test/unit/lib/cli/interactive-setup/aws-credentials.test.js b/test/unit/lib/cli/interactive-setup/aws-credentials.test.js index 17ab88044..ab73d8117 100644 --- a/test/unit/lib/cli/interactive-setup/aws-credentials.test.js +++ b/test/unit/lib/cli/interactive-setup/aws-credentials.test.js @@ -197,7 +197,7 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { ).to.be.true; }); - it('Should emit warning when dashboard is not available when fetching providers', async () => { + it('Should be ineffective dashboard is not available', async () => { const internalMockedSdk = { ...mockedSdk, getProviders: async () => { @@ -213,20 +213,13 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { '@serverless/dashboard-plugin/lib/isAuthenticated': () => true, }); - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => - expect( - await mockedStep.isApplicable({ - serviceDir: process.cwd(), - configuration: { provider: { name: 'aws' }, org: 'someorg' }, - configurationFilename: 'serverless.yml', - }) - ).to.be.false - ); - - expect(stdoutData).to.include('Serverless Dashboard is currently unavailable'); + expect( + await mockedStep.isApplicable({ + serviceDir: process.cwd(), + configuration: { provider: { name: 'aws' }, org: 'someorg' }, + configurationFilename: 'serverless.yml', + }) + ).to.be.false; }); it('Should be effective, at AWS service and no credentials are set', async () => @@ -238,7 +231,7 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { }) ).to.equal(true)); - it('Should emit a message when user decides to skip credentials setup', async () => { + it('Should allow to skip credentials setup', async () => { configureInquirerStub(inquirer, { list: { credentialsSetupChoice: '_skip_' }, }); @@ -256,7 +249,6 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { async () => await step.run(context) ); - expect(stdoutData).to.include('You can setup your AWS account later'); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([['credentialsSetupChoice', '_skip_']]) ); @@ -464,7 +456,6 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { list: { credentialsSetupChoice: '_create_provider_' }, }); - let stdoutData = ''; const context = { serviceDir: process.cwd(), configuration: { @@ -477,12 +468,8 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { configurationFilename: 'serverless.yml', stepHistory: new StepHistory(), }; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await mockedStep.run(context) - ); + await mockedStep.run(context); - expect(stdoutData).to.include('AWS Access Role provider was successfully created'); expect(mockedOpenBrowser).to.have.been.calledWith( 'https://app.serverless.com/someorg/settings/providers?source=cli&providerId=new&provider=aws' ); @@ -551,13 +538,8 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { configurationFilename: 'serverless.yml', stepHistory: new StepHistory(), }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await mockedStep.run(context) - ); + await mockedStep.run(context); - expect(stdoutData).to.include('AWS Access Role provider was successfully created'); expect(mockedOpenBrowser).to.have.been.calledWith( 'https://app.serverless.com/someorg/settings/providers?source=cli&providerId=new&provider=aws' ); @@ -605,13 +587,8 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { configurationFilename: 'serverless.yml', stepHistory: new StepHistory(), }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await mockedStep.run(context) - ); + await mockedStep.run(context); - expect(stdoutData).to.include('Serverless Dashboard is currently unavailable'); expect(mockedOpenBrowser).to.have.been.calledWith( 'https://app.serverless.com/someorg/settings/providers?source=cli&providerId=new&provider=aws' ); @@ -664,11 +641,7 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { configurationFilename: 'serverless.yml', stepHistory: new StepHistory(), }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await mockedStep.run(context) - ); + await mockedStep.run(context); expect(mockedCreateProviderLink).to.have.been.calledWith( 'org-uid', @@ -676,13 +649,12 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { 'appName|someapp|serviceName|someservice|stage|dev|region|us-east-1', 'provideruid' ); - expect(stdoutData).to.include('Selected provider was successfully linked'); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([['credentialsSetupChoice', '_user_choice_']]) ); }); - it('Should emit a warning when dashboard is not available and link cannot be created', async () => { + it('Should handle gently dashboard unavailabiilty when linking provider', async () => { const providerUid = 'provideruid'; const mockedCreateProviderLink = sinon.stub().callsFake(async () => { const err = new Error('error'); @@ -730,16 +702,7 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { stepHistory: new StepHistory(), configurationFilename: 'serverless.yml', }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await mockedStep.run(context) - ); - - expect(stdoutData).to.include( - 'Serverless Dashboard is currently unavailable, please try again later' - ); - expect(stdoutData).not.to.include('Selected provider was successfully linked'); + await mockedStep.run(context); expect(mockedCreateProviderLink).to.have.been.calledWith( 'org-uid', 'instance', @@ -752,7 +715,7 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { ); }); - it('Should emit a warning when dashboard is not available when fetching providers', async () => { + it('Should handle gently dashboard unavailabiilty when fetching providers', async () => { const internalMockedSdk = { ...mockedSdk, getProviders: async () => { @@ -768,19 +731,12 @@ describe('test/unit/lib/cli/interactive-setup/aws-credentials.test.js', () => { '@serverless/dashboard-plugin/lib/isAuthenticated': () => true, }); - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => - await mockedStep.run({ - serviceDir: process.cwd(), - configuration: { provider: { name: 'aws' }, org: 'someorg' }, - options: {}, - configurationFilename: 'serverless.yml', - }) - ); - - expect(stdoutData).to.include('Serverless Dashboard is currently unavailable'); + await mockedStep.run({ + serviceDir: process.cwd(), + configuration: { provider: { name: 'aws' }, org: 'someorg' }, + options: {}, + configurationFilename: 'serverless.yml', + }); }); }); }); diff --git a/test/unit/lib/cli/interactive-setup/dashboard-login.test.js b/test/unit/lib/cli/interactive-setup/dashboard-login.test.js index 51835424b..8061bd02b 100644 --- a/test/unit/lib/cli/interactive-setup/dashboard-login.test.js +++ b/test/unit/lib/cli/interactive-setup/dashboard-login.test.js @@ -5,7 +5,6 @@ const sinon = require('sinon'); const proxyquire = require('proxyquire'); const overrideCwd = require('process-utils/override-cwd'); const configureInquirerStub = require('@serverless/test/configure-inquirer-stub'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const { StepHistory } = require('@serverless/utils/telemetry'); const inquirer = require('@serverless/utils/inquirer'); @@ -123,20 +122,11 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function inquirer, stepHistory: new StepHistory(), }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - await loginStep.run(context); - } - ); + await loginStep.run(context); expect(loginStub.calledOnce).to.be.true; expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([['shouldLoginOrRegister', true]]) ); - expect(stdoutData).to.include( - 'Enable Serverless Dashboard to get enhanced monitoring, logs and secrets management: https://serverless.com/monitoring' - ); }); it('Should login and skip question when user providers `org` option', async () => { @@ -198,19 +188,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-login.test.js', function inquirer, stepHistory: new StepHistory(), }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - await loginStep.run(context); - } - ); + await loginStep.run(context); expect(loginStub.called).to.be.false; expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([['shouldLoginOrRegister', false]]) ); - expect(stdoutData).to.include( - 'Enable Serverless Dashboard to get enhanced monitoring, logs and secrets management: https://serverless.com/monitoring' - ); }); }); diff --git a/test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js b/test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js index d8d2e7222..491311be9 100644 --- a/test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js +++ b/test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js @@ -7,10 +7,8 @@ const proxyquire = require('proxyquire'); const fsp = require('fs').promises; const yaml = require('js-yaml'); const overrideCwd = require('process-utils/override-cwd'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const overrideEnv = require('process-utils/override-env'); const configureInquirerStub = require('@serverless/test/configure-inquirer-stub'); -const stripAnsi = require('strip-ansi'); const { StepHistory } = require('@serverless/utils/telemetry'); const inquirer = require('@serverless/utils/inquirer'); @@ -328,16 +326,11 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi inquirer, stepHistory: new StepHistory(), }; - let stdoutData = ''; + await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -346,9 +339,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app other-app' - ); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([ ['orgName', '_user_choice_'], @@ -373,17 +363,11 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi inquirer, stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideEnv({ variables: { SERVERLESS_ACCESS_KEY: 'validkey' } }, async () => { await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); }); const serviceConfig = yaml.load( @@ -393,9 +377,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('fromaccesskey'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org fromaccesskey and app other-app' - ); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([ ['orgName', '_user_choice_'], @@ -420,17 +401,11 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi inquirer, stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideEnv({ variables: { SERVERLESS_ACCESS_KEY: 'validkey' } }, async () => { await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); }); const serviceConfig = yaml.load( @@ -600,16 +575,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -618,9 +587,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal(configuration.service); expect(context.configuration.org).to.equal('orgwithoutapps'); expect(context.configuration.app).to.equal(configuration.service); - expect(stripAnsi(stdoutData)).to.include( - `Your project has been setup with org orgwithoutapps and app ${configuration.service}` - ); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map()); }); }); @@ -641,16 +607,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -659,9 +619,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app other-app' - ); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map()); }); @@ -683,16 +640,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -701,9 +652,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('app-from-flag'); expect(context.configuration.org).to.equal('otherorg'); expect(context.configuration.app).to.equal('app-from-flag'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org otherorg and app app-from-flag' - ); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([['shouldOverrideDashboardConfig', true]]) ); @@ -758,16 +706,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -776,9 +718,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app other-app' - ); + expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([ ['orgName', '_user_choice_'], @@ -807,16 +747,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -825,9 +759,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('otherorg'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org otherorg and app other-app' - ); + expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([ ['orgName', '_user_choice_'], @@ -855,16 +787,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi history: new Map(), stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -873,9 +799,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app other-app' - ); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([ ['orgName', '_user_choice_'], @@ -903,16 +826,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -921,9 +838,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app other-app' - ); + expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([['appName', '_user_choice_']]) ); @@ -947,16 +862,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi history: new Map(), stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -965,9 +874,7 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('frominput'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('frominput'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app frominput' - ); + expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([ ['orgName', '_user_choice_'], @@ -997,16 +904,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi history: new Map(), stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -1015,9 +916,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app other-app' - ); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([ ['shouldUpdateOrg', true], @@ -1047,16 +945,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -1065,9 +957,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app other-app' - ); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([['appName', '_user_choice_']]) ); @@ -1090,16 +979,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -1108,9 +991,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('app-from-flag'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('app-from-flag'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app app-from-flag' - ); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map()); }); @@ -1130,16 +1010,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -1148,9 +1022,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal(configuration.service); expect(context.configuration.org).to.equal('orgwithoutapps'); expect(context.configuration.app).to.equal(configuration.service); - expect(stripAnsi(stdoutData)).to.include( - `Your project has been setup with org orgwithoutapps and app ${configuration.service}` - ); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map()); }); @@ -1172,16 +1043,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -1190,9 +1055,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app other-app' - ); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([['appName', '_user_choice_']]) ); @@ -1224,16 +1086,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -1242,9 +1098,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('not-created-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('not-created-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app not-created-app' - ); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([['appUpdateType', '_create_']]) ); @@ -1274,16 +1127,10 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi stepHistory: new StepHistory(), }; - let stdoutData = ''; await overrideCwd(serviceDir, async () => { - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const stepData = await step.isApplicable(context); - if (!stepData) throw new Error('Step resolved as not applicable'); - await step.run(context, stepData); - } - ); + const stepData = await step.isApplicable(context); + if (!stepData) throw new Error('Step resolved as not applicable'); + await step.run(context, stepData); }); const serviceConfig = yaml.load( String(await fsp.readFile(join(serviceDir, 'serverless.yml'))) @@ -1292,9 +1139,6 @@ describe('test/unit/lib/cli/interactive-setup/dashboard-set-org.test.js', functi expect(serviceConfig.app).to.equal('other-app'); expect(context.configuration.org).to.equal('testinteractivecli'); expect(context.configuration.app).to.equal('other-app'); - expect(stripAnsi(stdoutData)).to.include( - 'Your project has been setup with org testinteractivecli and app other-app' - ); expect(context.stepHistory.valuesMap()).to.deep.equal( new Map([ ['appUpdateType', '_choose_existing_'], diff --git a/test/unit/lib/cli/interactive-setup/deploy.test.js b/test/unit/lib/cli/interactive-setup/deploy.test.js index 271ea53d1..8d26082e3 100644 --- a/test/unit/lib/cli/interactive-setup/deploy.test.js +++ b/test/unit/lib/cli/interactive-setup/deploy.test.js @@ -1,13 +1,11 @@ 'use strict'; -const stripAnsi = require('strip-ansi'); const chai = require('chai'); const sinon = require('sinon'); const configureInquirerStub = require('@serverless/test/configure-inquirer-stub'); const overrideEnv = require('process-utils/override-env'); const step = require('../../../../../lib/cli/interactive-setup/deploy'); const proxyquire = require('proxyquire'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const { StepHistory } = require('@serverless/utils/telemetry'); const { expect } = chai; @@ -89,16 +87,8 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => { isInServiceContext: false, }, }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await step.run(context) - ); + await step.run(context); - expect(stripAnsi(stdoutData)).to.include( - 'Your project is ready for deployment and available in' - ); - expect(stripAnsi(stdoutData)).to.include('Run serverless in the project directory'); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', false]])); }); @@ -119,14 +109,8 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => { isInServiceContext: true, }, }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await step.run(context) - ); + await step.run(context); - expect(stripAnsi(stdoutData)).to.include('Your project is ready for deployment\n'); - expect(stripAnsi(stdoutData)).to.include('Run serverless in the project directory'); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', false]])); }); @@ -149,18 +133,8 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => { isInServiceContext: false, }, }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await step.run(context) - ); + await step.run(context); - expect(stripAnsi(stdoutData)).to.include( - 'Your project is ready for deployment and available in' - ); - expect(stripAnsi(stdoutData)).to.include( - 'Invoke your functions and view logs in the dashboard' - ); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', false]])); }); @@ -183,16 +157,8 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => { isInServiceContext: true, }, }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await step.run(context) - ); + await step.run(context); - expect(stripAnsi(stdoutData)).to.include('Your project is ready for deployment\n'); - expect(stripAnsi(stdoutData)).to.include( - 'Invoke your functions and view logs in the dashboard' - ); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', false]])); }); @@ -243,16 +209,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => { isInServiceContext: false, }, }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await mockedStep.run(context) - ); - - expect(stripAnsi(stdoutData)).to.include('Your project is live and available'); - expect(stripAnsi(stdoutData)).to.include( - 'Open https://app.serverless-dev.com/path/to/dashboard' - ); + await mockedStep.run(context); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', true]])); }); @@ -304,16 +261,7 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => { isInServiceContext: true, }, }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await mockedStep.run(context) - ); - - expect(stripAnsi(stdoutData)).to.include('Your project is live\n'); - expect(stripAnsi(stdoutData)).to.include( - 'Open https://app.serverless-dev.com/path/to/dashboard' - ); + await mockedStep.run(context); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', true]])); }); @@ -359,14 +307,8 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => { isInServiceContext: false, }, }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await mockedStep.run(context) - ); + await mockedStep.run(context); - expect(stripAnsi(stdoutData)).to.include('Your project is live and available'); - expect(stripAnsi(stdoutData)).to.include('Run serverless'); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', true]])); }); @@ -411,14 +353,8 @@ describe('test/unit/lib/cli/interactive-setup/deploy.test.js', () => { isInServiceContext: true, }, }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => await mockedStep.run(context) - ); + await mockedStep.run(context); - expect(stripAnsi(stdoutData)).to.include('Your project is live\n'); - expect(stripAnsi(stdoutData)).to.include('Run serverless'); expect(context.stepHistory.valuesMap()).to.deep.equal(new Map([['shouldDeploy', true]])); }); }); diff --git a/test/unit/lib/cli/interactive-setup/service.test.js b/test/unit/lib/cli/interactive-setup/service.test.js index 48e69a432..695dbd54c 100644 --- a/test/unit/lib/cli/interactive-setup/service.test.js +++ b/test/unit/lib/cli/interactive-setup/service.test.js @@ -6,7 +6,6 @@ const sinon = require('sinon'); const configureInquirerStub = require('@serverless/test/configure-inquirer-stub'); const step = require('../../../../../lib/cli/interactive-setup/service'); const proxyquire = require('proxyquire'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const ServerlessError = require('../../../../../lib/serverless-error'); const { StepHistory } = require('@serverless/utils/telemetry'); @@ -208,51 +207,6 @@ describe('test/unit/lib/cli/interactive-setup/service.test.js', () => { ); }); - it('Should emit warning if npm installation not found', async () => { - const downloadTemplateFromRepoStub = sinon.stub(); - const mockedStep = proxyquire('../../../../../lib/cli/interactive-setup/service', { - 'child-process-ext/spawn': sinon.stub().rejects({ code: 'ENOENT' }), - '../../utils/downloadTemplateFromRepo': { - downloadTemplateFromRepo: downloadTemplateFromRepoStub.callsFake( - async (templateUrl, projectType, projectName) => { - await fsp.mkdir(projectName); - const serverlessYmlContent = ` - service: service - provider: - name: aws - `; - - await fsp.writeFile(path.join(projectName, 'serverless.yml'), serverlessYmlContent); - await fsp.writeFile(path.join(projectName, 'package.json'), '{}'); - } - ), - }, - }); - - configureInquirerStub(inquirer, { - list: { projectType: 'aws-nodejs' }, - input: { projectName: 'test-project-missing-npm' }, - }); - - const context = { options: {}, stepHistory: new StepHistory() }; - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => mockedStep.run(context) - ); - - const stats = await fsp.lstat('test-project-missing-npm/serverless.yml'); - expect(stats.isFile()).to.be.true; - expect(stdoutData).to.include('Cannot install dependencies'); - - expect(context.stepHistory.valuesMap()).to.deep.equal( - new Map([ - ['projectType', 'aws-nodejs'], - ['projectName', '_user_input_'], - ]) - ); - }); - it('Should emit warning if npm installation not found', async () => { const downloadTemplateFromRepoStub = sinon.stub(); const mockedStep = proxyquire('../../../../../lib/cli/interactive-setup/service', { diff --git a/test/unit/lib/cli/render-help/command.test.js b/test/unit/lib/cli/render-help/command.test.js index 02e8a8e17..6fb7d00ab 100644 --- a/test/unit/lib/cli/render-help/command.test.js +++ b/test/unit/lib/cli/render-help/command.test.js @@ -1,31 +1,23 @@ 'use strict'; const { expect } = require('chai'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const renderCommandHelp = require('../../../../../lib/cli/render-help/command'); const commandsSchema = require('../../../../../lib/cli/commands-schema'); +const observeOutput = require('@serverless/test/observe-output'); describe('test/unit/lib/cli/render-help/command.test.js', () => { - it('should show help', () => { - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => renderCommandHelp('deploy') - ); - expect(stdoutData).to.have.string('deploy'); - expect(stdoutData).to.have.string('deploy function'); - expect(stdoutData).to.have.string('--help'); - expect(stdoutData).to.have.string(commandsSchema.get('deploy').usage); - expect(stdoutData).to.have.string(commandsSchema.get('deploy function').usage); + it('should show help', async () => { + const output = await observeOutput(() => renderCommandHelp('deploy')); + expect(output).to.have.string('deploy'); + expect(output).to.have.string('deploy function'); + expect(output).to.have.string('--help'); + expect(output).to.have.string(commandsSchema.get('deploy').usage); + expect(output).to.have.string(commandsSchema.get('deploy function').usage); }); - it('should show help for container command', () => { - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => renderCommandHelp('config tabcompletion') - ); - expect(stdoutData).to.have.string('config tabcompletion install'); - expect(stdoutData).to.have.string(commandsSchema.get('config tabcompletion install').usage); - expect(stdoutData).to.have.string(commandsSchema.get('config tabcompletion uninstall').usage); + it('should show help for container command', async () => { + const output = await observeOutput(() => renderCommandHelp('config tabcompletion')); + expect(output).to.have.string('config tabcompletion install'); + expect(output).to.have.string(commandsSchema.get('config tabcompletion install').usage); + expect(output).to.have.string(commandsSchema.get('config tabcompletion uninstall').usage); }); }); diff --git a/test/unit/lib/cli/render-help/general.test.js b/test/unit/lib/cli/render-help/general.test.js index 9e5fb8b3e..240a1d633 100644 --- a/test/unit/lib/cli/render-help/general.test.js +++ b/test/unit/lib/cli/render-help/general.test.js @@ -1,17 +1,13 @@ 'use strict'; const { expect } = require('chai'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const renderGeneralHelp = require('../../../../../lib/cli/render-help/general'); +const observeOutput = require('@serverless/test/observe-output'); describe('test/unit/lib/cli/render-help/general.test.js', () => { - it('should show help', () => { - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => renderGeneralHelp(new Set()) - ); - expect(stdoutData).to.have.string('General Commands'); - expect(stdoutData).to.have.string('deploy function'); + it('should show help', async () => { + const output = await observeOutput(() => renderGeneralHelp(new Set())); + expect(output).to.have.string('Usage'); + expect(output).to.have.string('deploy function'); }); }); diff --git a/test/unit/lib/cli/render-help/index.test.js b/test/unit/lib/cli/render-help/index.test.js index 986c45a16..54d6f0cc0 100644 --- a/test/unit/lib/cli/render-help/index.test.js +++ b/test/unit/lib/cli/render-help/index.test.js @@ -3,11 +3,11 @@ const { expect } = require('chai'); const overrideArgv = require('process-utils/override-argv'); const resolveInput = require('../../../../../lib/cli/resolve-input'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const renderHelp = require('../../../../../lib/cli/render-help'); +const observeOutput = require('@serverless/test/observe-output'); describe('test/unit/lib/cli/render-help/index.test.js', () => { - it('should show general help on main command', () => { + it('should show general help on main command', async () => { resolveInput.clear(); overrideArgv( { @@ -15,16 +15,12 @@ describe('test/unit/lib/cli/render-help/index.test.js', () => { }, () => resolveInput() ); - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => renderHelp(new Set()) - ); - expect(stdoutData).to.have.string('General Commands'); - expect(stdoutData).to.have.string('deploy function'); + const output = await observeOutput(() => renderHelp(new Set())); + expect(output).to.have.string('Usage'); + expect(output).to.have.string('deploy function'); }); - it('should show interactive help when requested', () => { + it('should show interactive help when requested', async () => { resolveInput.clear(); overrideArgv( { @@ -32,16 +28,12 @@ describe('test/unit/lib/cli/render-help/index.test.js', () => { }, () => resolveInput() ); - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => renderHelp(new Set()) - ); - expect(stdoutData).to.have.string('Interactive CLI'); - expect(stdoutData).to.have.string('--help-interactive'); + const output = await observeOutput(() => renderHelp(new Set())); + expect(output).to.have.string('Interactive CLI'); + expect(output).to.have.string('--help-interactive'); }); - it('should show general help on help command', () => { + it('should show general help on help command', async () => { resolveInput.clear(); overrideArgv( { @@ -49,16 +41,12 @@ describe('test/unit/lib/cli/render-help/index.test.js', () => { }, () => resolveInput() ); - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => renderHelp(new Set()) - ); - expect(stdoutData).to.have.string('General Commands'); - expect(stdoutData).to.have.string('deploy function'); + const output = await observeOutput(() => renderHelp(new Set())); + expect(output).to.have.string('Usage'); + expect(output).to.have.string('deploy function'); }); - it('should show specific commmand help with specific command', () => { + it('should show specific commmand help with specific command', async () => { resolveInput.clear(); const { commandsSchema } = overrideArgv( { @@ -66,15 +54,11 @@ describe('test/unit/lib/cli/render-help/index.test.js', () => { }, () => resolveInput() ); - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => renderHelp(new Set()) - ); - expect(stdoutData).to.have.string('deploy'); - expect(stdoutData).to.have.string('deploy function'); - expect(stdoutData).to.have.string('--help'); - expect(stdoutData).to.have.string(commandsSchema.get('deploy').usage); - expect(stdoutData).to.have.string(commandsSchema.get('deploy function').usage); + const output = await observeOutput(() => renderHelp(new Set())); + expect(output).to.have.string('deploy'); + expect(output).to.have.string('deploy function'); + expect(output).to.have.string('--help'); + expect(output).to.have.string(commandsSchema.get('deploy').usage); + expect(output).to.have.string(commandsSchema.get('deploy function').usage); }); }); diff --git a/test/unit/lib/cli/render-help/interactive-setup.test.js b/test/unit/lib/cli/render-help/interactive-setup.test.js index 7bdf98bc2..03ed8c0ff 100644 --- a/test/unit/lib/cli/render-help/interactive-setup.test.js +++ b/test/unit/lib/cli/render-help/interactive-setup.test.js @@ -1,17 +1,13 @@ 'use strict'; const { expect } = require('chai'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const renderInteractiveSetupHelp = require('../../../../../lib/cli/render-help/interactive-setup'); +const observeOutput = require('@serverless/test/observe-output'); describe('test/unit/lib/cli/render-help/interactive-setup.test.js', () => { - it('should show help', () => { - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => renderInteractiveSetupHelp() - ); - expect(stdoutData).to.have.string('Interactive CLI'); - expect(stdoutData).to.have.string('--help-interactive'); + it('should show help', async () => { + const output = await observeOutput(() => renderInteractiveSetupHelp()); + expect(output).to.have.string('Interactive CLI'); + expect(output).to.have.string('--help-interactive'); }); }); diff --git a/test/unit/lib/cli/render-help/options.test.js b/test/unit/lib/cli/render-help/options.test.js index 81ee6f9cd..901440504 100644 --- a/test/unit/lib/cli/render-help/options.test.js +++ b/test/unit/lib/cli/render-help/options.test.js @@ -1,29 +1,26 @@ 'use strict'; const { expect } = require('chai'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const renderOptionsHelp = require('../../../../../lib/cli/render-help/options'); +const observeOutput = require('@serverless/test/observe-output'); describe('test/unit/lib/cli/render-help/options.test.js', () => { - it('should list options', () => { - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => - renderOptionsHelp({ - foo: { - usage: 'Some option', - shortcut: 'b', - required: true, - }, - bar: { - usage: 'Elo', - }, - noData: {}, - }) + it('should list options', async () => { + const output = await observeOutput(() => + renderOptionsHelp({ + foo: { + usage: 'Some option', + shortcut: 'b', + required: true, + }, + bar: { + usage: 'Elo', + }, + noData: {}, + }) ); - expect(stdoutData).to.have.string('--foo'); - expect(stdoutData).to.have.string('-b'); - expect(stdoutData).to.have.string('Some option'); + expect(output).to.have.string('--foo'); + expect(output).to.have.string('-b'); + expect(output).to.have.string('Some option'); }); }); diff --git a/test/unit/lib/cli/render-version.test.js b/test/unit/lib/cli/render-version.test.js index 1ec9cf8a0..fbe4d2392 100644 --- a/test/unit/lib/cli/render-version.test.js +++ b/test/unit/lib/cli/render-version.test.js @@ -1,17 +1,13 @@ 'use strict'; const { expect } = require('chai'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); const listVersion = require('../../../../lib/cli/render-version'); +const observeOutput = require('@serverless/test/observe-output'); describe('test/unit/lib/cli/list-version.test.js', () => { it('should log version', async () => { - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - () => listVersion() - ); - expect(stdoutData).to.have.string('Framework Core: '); - expect(stdoutData).to.have.string('SDK: '); + const output = await observeOutput(() => listVersion()); + expect(output).to.have.string('Framework Core: '); + expect(output).to.have.string('SDK: '); }); }); diff --git a/test/unit/lib/configSchema.test.js b/test/unit/lib/configSchema.test.js index 84b8a2756..beabbf6e0 100644 --- a/test/unit/lib/configSchema.test.js +++ b/test/unit/lib/configSchema.test.js @@ -63,7 +63,6 @@ describe('test/unit/lib/configSchema.test.js', () => { }, { isValid: true, - logMessage: 'Unrecognized provider', description: 'provider not existing name', mutation: { provider: { name: 'awssss' }, @@ -93,11 +92,10 @@ describe('test/unit/lib/configSchema.test.js', () => { configExt: someCase.mutation, command: 'info', }).then( - ({ stdoutData }) => { + () => { if (!someCase.isValid) { expect(false).to.be.true; } - if (someCase.logMessage) expect(stdoutData).to.include(someCase.logMessage); return; }, (err) => { diff --git a/test/unit/lib/plugins/aws/deploy/lib/checkForChanges.test.js b/test/unit/lib/plugins/aws/deploy/lib/checkForChanges.test.js index 7b6d93ddb..5dc4dfb1e 100644 --- a/test/unit/lib/plugins/aws/deploy/lib/checkForChanges.test.js +++ b/test/unit/lib/plugins/aws/deploy/lib/checkForChanges.test.js @@ -1071,31 +1071,6 @@ describe('test/unit/lib/plugins/aws/deploy/lib/checkForChanges.test.js', () => { expect(serverless.service.provider.shouldNotDeploy).to.equal(true); }); - it('should print a warning if missing lambda:GetFunction permission', async () => { - const { stdoutData } = await runServerless({ - fixture: 'checkForChanges', - command: 'deploy', - lastLifecycleHookName: 'aws:deploy:deploy:checkForChanges', - awsRequestStubMap: { - ...commonAwsSdkMock, - Lambda: { - getFunction: sandbox.stub().throws({ providerError: { statusCode: 403 } }), - }, - S3: { - listObjectsV2: {}, - headObjects: {}, - headBucket: {}, - }, - }, - }); - expect(stdoutData).to.include( - [ - 'WARNING: Not authorized to perform: lambda:GetFunction for at least one of the lambda functions.', - ' Deployment will not be skipped even if service files did not change.', - ].join('') - ); - }); - it.skip('TODO: should crash meaningfully if bucket does not exist', () => { // Replaces: // https://github.com/serverless/serverless/blob/11fb14115ea47d53a61fa666a94e60d585fb3a4d/test/unit/lib/plugins/aws/deploy/lib/checkForChanges.test.js#L137-L149 diff --git a/test/unit/lib/plugins/aws/deploy/lib/extendedValidate.test.js b/test/unit/lib/plugins/aws/deploy/lib/extendedValidate.test.js index a3da6edef..c7f510827 100644 --- a/test/unit/lib/plugins/aws/deploy/lib/extendedValidate.test.js +++ b/test/unit/lib/plugins/aws/deploy/lib/extendedValidate.test.js @@ -7,7 +7,6 @@ const AwsProvider = require('../../../../../../../lib/plugins/aws/provider'); const AwsDeploy = require('../../../../../../../lib/plugins/aws/deploy/index'); const Serverless = require('../../../../../../../lib/Serverless'); const { getTmpDirPath } = require('../../../../../../utils/fs'); -const runServerless = require('../../../../../../utils/run-serverless'); chai.use(require('sinon-chai')); @@ -165,46 +164,3 @@ describe('extendedValidate', () => { }); }); }); - -describe('test/unit/lib/plugins/aws/deploy/lib/extendedValidate.test.js', () => { - it("should not warn if function's timeout is greater than 30 and it's attached to APIGW, but it has [async] mode", async () => { - const msg = [ - "WARNING: Function foo has timeout of 31 seconds, however, it's", - "attached to API Gateway so it's automatically limited to 30 seconds.", - ].join(' '); - - const { stdoutData } = await runServerless({ - fixture: 'function', - configExt: { - functions: { - basic: { - timeout: 31, - events: [ - { - http: { - method: 'GET', - path: '/foo', - async: true, - }, - }, - ], - }, - }, - }, - awsRequestStubMap: { - STS: { - getCallerIdentity: { - ResponseMetadata: { RequestId: 'ffffffff-ffff-ffff-ffff-ffffffffffff' }, - UserId: 'XXXXXXXXXXXXXXXXXXXXX', - Account: '1234567890', - Arn: 'arn:aws:iam::1234567890:user/test', - }, - }, - }, - command: 'deploy', - lastLifecycleHookName: 'before:deploy:deploy', - }); - - expect(stdoutData.includes(msg)).to.be.equal(false); - }); -}); diff --git a/test/unit/lib/plugins/aws/deployFunction.test.js b/test/unit/lib/plugins/aws/deployFunction.test.js index 012ad403e..8643acdeb 100644 --- a/test/unit/lib/plugins/aws/deployFunction.test.js +++ b/test/unit/lib/plugins/aws/deployFunction.test.js @@ -398,7 +398,7 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => { }); it('should skip updating function configuration if image config did not change', async () => { - const { stdoutData } = await runServerless({ + await runServerless({ fixture: 'function', command: 'deploy function', options: { function: 'basic' }, @@ -439,13 +439,10 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => { }, }); expect(updateFunctionConfigurationStub).not.to.be.called; - expect(stdoutData).to.include( - 'Configuration did not change. Skipping function configuration update.' - ); }); it('should skip deployment if image sha did not change', async () => { - const { stdoutData } = await runServerless({ + await runServerless({ fixture: 'function', command: 'deploy function', options: { function: 'basic' }, @@ -472,7 +469,6 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => { }, }); expect(updateFunctionCodeStub).not.to.be.called; - expect(stdoutData).to.include('Image did not change. Skipping function deployment.'); }); it('should fail if function with image was previously defined with handler', async () => { @@ -543,7 +539,7 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => { .throws({ providerError: { code: 'ResourceConflictException' } }) .onSecondCall() .resolves({}); - const { stdoutData } = await runServerless({ + await runServerless({ fixture: 'function', command: 'deploy function', options: { function: 'basic' }, @@ -566,12 +562,11 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => { }, }); - expect(stdoutData).to.include('Retrying configuration update for function'); expect(innerUpdateFunctionConfigurationStub.callCount).to.equal(2); }); it('should update function configuration if configuration changed', async () => { - const { stdoutData } = await runServerless({ + await runServerless({ fixture: 'function', command: 'deploy function', options: { function: 'basic' }, @@ -641,11 +636,10 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => { }, Layers: [layerArn, secondLayerArn], }); - expect(stdoutData).to.include('Successfully updated function'); }); it('should skip updating properties that contain references', async () => { - const { stdoutData } = await runServerless({ + await runServerless({ fixture: 'function', command: 'deploy function', options: { function: 'basic' }, @@ -692,11 +686,10 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => { }, Role: role, }); - expect(stdoutData).to.include('Successfully updated function'); }); it('should update function configuration with provider-level properties', async () => { - const { stdoutData } = await runServerless({ + await runServerless({ fixture: 'function', command: 'deploy function', options: { function: 'basic' }, @@ -753,11 +746,10 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => { SubnetIds: ['subnet-111', 'subnet-222'], }, }); - expect(stdoutData).to.include('Successfully updated function'); }); it('should not update function configuration if configuration did not change', async () => { - const { stdoutData } = await runServerless({ + await runServerless({ fixture: 'function', command: 'deploy function', options: { function: 'basic' }, @@ -828,9 +820,6 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => { }); expect(updateFunctionConfigurationStub).not.to.be.called; - expect(stdoutData).to.include( - 'Configuration did not change. Skipping function configuration update.' - ); }); it('configuration uses the "kmsKeyArn" instead of functionObj.awsKmsKeyArn', async () => { diff --git a/test/unit/lib/plugins/aws/invoke.test.js b/test/unit/lib/plugins/aws/invoke.test.js index 709342d35..89c6f9672 100644 --- a/test/unit/lib/plugins/aws/invoke.test.js +++ b/test/unit/lib/plugins/aws/invoke.test.js @@ -63,9 +63,7 @@ describe('test/unit/lib/plugins/aws/invoke.test.js', () => { }); it('should log payload', () => { - expect(result.stdoutData).to.contain( - '{\n "inputKey": "inputValue"\n}\n\u001b[90m--------------------------------------------------------------------\u001b[39m\ntest\n' - ); + expect(result.output).to.contain('"inputKey": "inputValue"'); }); }); diff --git a/test/unit/lib/plugins/aws/invokeLocal/index.test.js b/test/unit/lib/plugins/aws/invokeLocal/index.test.js index c8253f14d..560f50a96 100644 --- a/test/unit/lib/plugins/aws/invokeLocal/index.test.js +++ b/test/unit/lib/plugins/aws/invokeLocal/index.test.js @@ -970,7 +970,7 @@ describe('test/unit/lib/plugins/aws/invokeLocal/index.test.js', () => { process.env.AWS_ACCESS_KEY_ID = 'AAKIXXX'; process.env.AWS_SECRET_ACCESS_KEY = 'ASAKXXX'; - // Confirm outcome on { stdout } + // Confirm outcome on { output } const response = await runServerless({ fixture: 'invocation', command: 'invoke local', @@ -997,8 +997,8 @@ describe('test/unit/lib/plugins/aws/invokeLocal/index.test.js', () => { }, }, }); - const stdoutAsJson = JSON.parse(response.stdoutData); - responseBody = JSON.parse(stdoutAsJson.body); + const outputAsJson = JSON.parse(response.output); + responseBody = JSON.parse(outputAsJson.body); }); after(() => { @@ -1046,33 +1046,33 @@ describe('test/unit/lib/plugins/aws/invokeLocal/index.test.js', () => { testRuntime('callback'); it('should support success resolution via async function', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'async' }, }); - expect(stdoutData).to.include('Invoked'); + expect(output).to.include('Invoked'); }); it('should support success resolution via context.done', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'contextDone' }, }); - expect(stdoutData).to.include('Invoked'); + expect(output).to.include('Invoked'); }); it('should support success resolution via context.succeed', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'contextSucceed' }, }); - expect(stdoutData).to.include('Invoked'); + expect(output).to.include('Invoked'); }); it('should support immediate failure at initialization', async () => { @@ -1099,69 +1099,69 @@ describe('test/unit/lib/plugins/aws/invokeLocal/index.test.js', () => { }); it('should support failure resolution via async function', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'async', data: '{"shouldFail":true}' }, }); - expect(stdoutData).to.include('Failed on request'); + expect(output).to.include('Failed on request'); }); it('should support failure resolution via callback', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'callback', data: '{"shouldFail":true}' }, }); - expect(stdoutData).to.include('Failed on request'); + expect(output).to.include('Failed on request'); }); it('should support failure resolution via context.done', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'contextDone', data: '{"shouldFail":true}' }, }); - expect(stdoutData).to.include('Failed on request'); + expect(output).to.include('Failed on request'); }); it('should support failure resolution via context.fail', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'contextSucceed', data: '{"shouldFail":true}' }, }); - expect(stdoutData).to.include('Failed on request'); + expect(output).to.include('Failed on request'); }); it('should recognize first resolution', async () => { - const { stdoutData: firstRunStdout } = await runServerless({ + const { output: firstRunOutput } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'doubledResolutionCallbackFirst' }, }); - const { stdoutData: secondRunStdout } = await runServerless({ + const { output: secondRunOutput } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'doubledResolutionPromiseFirst' }, }); - expect(firstRunStdout).to.include('callback'); - expect(secondRunStdout).to.include('promise'); + expect(firstRunOutput).to.include('callback'); + expect(secondRunOutput).to.include('promise'); }); it('should support context.remainingTimeInMillis()', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'remainingTime' }, }); - const body = JSON.parse(stdoutData).body; + const body = JSON.parse(output).body; const [firstRemainingMs, secondRemainingMs, thirdRemainingMs] = JSON.parse(body).data; expect(firstRemainingMs).to.be.lte(3000); expect(secondRemainingMs).to.be.lte(2910); @@ -1182,13 +1182,13 @@ describe('test/unit/lib/plugins/aws/invokeLocal/index.test.js', () => { testRuntime('python'); describe('context.remainingTimeInMillis', () => { it('should support context.get_remaining_time_in_millis()', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'pythonRemainingTime' }, }); - const { start, stop } = JSON.parse(stdoutData); + const { start, stop } = JSON.parse(output); expect(start).to.lte(3000); expect(stop).to.lte(2910); }); @@ -1208,33 +1208,33 @@ describe('test/unit/lib/plugins/aws/invokeLocal/index.test.js', () => { testRuntime('ruby'); it('should support class/module address in handler for "ruby*" runtime', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'rubyClass' }, }); - expect(stdoutData).to.include('rubyclass'); + expect(output).to.include('rubyclass'); }); it('should support context.get_remaining_time_in_millis()', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'rubyRemainingTime' }, }); - const { start, stop } = JSON.parse(stdoutData); + const { start, stop } = JSON.parse(output); expect(start).to.lte(6000); expect(stop).to.lte(5910); }); it('should support context.deadline_ms', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'invocation', command: 'invoke local', options: { function: 'rubyDeadline' }, }); - const { deadlineMs } = JSON.parse(stdoutData); + const { deadlineMs } = JSON.parse(output); expect(deadlineMs).to.be.gt(Date.now()); }); }); diff --git a/test/unit/lib/plugins/aws/package/compile/functions.test.js b/test/unit/lib/plugins/aws/package/compile/functions.test.js index 57bee6e65..aad025e29 100644 --- a/test/unit/lib/plugins/aws/package/compile/functions.test.js +++ b/test/unit/lib/plugins/aws/package/compile/functions.test.js @@ -2582,7 +2582,7 @@ describe('lib/plugins/aws/package/compile/functions/index.test.js', () => { const originalVersionArn = originalTemplate.Outputs.BasicLambdaFunctionQualifiedArn.Value.Ref; - const { cfTemplate: updatedTemplate, stdoutData } = await runServerless({ + const { cfTemplate: updatedTemplate } = await runServerless({ cwd: serviceDir, command: 'deploy', lastLifecycleHookName: 'before:deploy:deploy', @@ -2596,7 +2596,6 @@ describe('lib/plugins/aws/package/compile/functions/index.test.js', () => { expect( updatedTemplate.Resources[awsNaming.getLambdaLogicalId('basic')].Properties.Description ).to.equal('temporary-description-to-enforce-hash-update'); - expect(stdoutData).to.include('Your service has been deployed with new hashing algorithm'); }); }); }); diff --git a/test/unit/lib/plugins/aws/provider.test.js b/test/unit/lib/plugins/aws/provider.test.js index 59e7b1f50..cd46508f0 100644 --- a/test/unit/lib/plugins/aws/provider.test.js +++ b/test/unit/lib/plugins/aws/provider.test.js @@ -1501,41 +1501,6 @@ aws_secret_access_key = CUSTOMSECRET ]); }); - it('should emit warning if docker login stores unencrypted credentials', async () => { - const awsRequestStubMap = { - ...baseAwsRequestStubMap, - ECR: { - ...baseAwsRequestStubMap.ECR, - describeRepositories: describeRepositoriesStub.resolves({ - repositories: [{ repositoryUri }], - }), - createRepository: createRepositoryStub, - }, - }; - - const { stdoutData } = await runServerless({ - fixture: 'ecr', - command: 'package', - awsRequestStubMap, - modulesCacheStub: { - ...modulesCacheStub, - 'child-process-ext/spawn': sinon - .stub() - .returns({ - stdBuffer: `digest: sha256:${imageSha} size: 1787`, - }) - .onCall(3) - .throws({ stdBuffer: 'no basic auth credentials' }) - .onCall(4) - .returns({ stdBuffer: 'your password will be stored unencrypted' }), - }, - }); - - expect(stdoutData).to.include( - 'WARNING: Docker authentication token will be stored unencrypted in docker config.' - ); - }); - it('should work correctly when image is defined with implicit path in provider', async () => { const awsRequestStubMap = { ...baseAwsRequestStubMap, diff --git a/test/unit/lib/plugins/aws/remove/index.test.js b/test/unit/lib/plugins/aws/remove/index.test.js index fff8f1383..2cd2a9d1b 100644 --- a/test/unit/lib/plugins/aws/remove/index.test.js +++ b/test/unit/lib/plugins/aws/remove/index.test.js @@ -212,21 +212,6 @@ describe('test/unit/lib/plugins/aws/remove/index.test.js', () => { }); }); - it('emits warning when repository cannot be accessed due to denied access but there are images defined', async () => { - describeRepositoriesStub.throws({ providerError: { code: 'AccessDeniedException' } }); - - const { stdoutData } = await runServerless({ - fixture: 'ecr', - command: 'remove', - awsRequestStubMap, - }); - - expect(stdoutData).to.include('WARNING: Could not access ECR repository due to denied access'); - expect(stdoutData).to.include('ECR repository removal will be skipped.'); - - expect(deleteRepositoryStub).not.to.be.called; - }); - it('should execute expected operations with versioning enabled if no object versions are present', async () => { const listObjectVersionsStub = sinon.stub().resolves(); diff --git a/test/unit/lib/plugins/print.test.js b/test/unit/lib/plugins/print.test.js index d20a61e86..877b7ea8e 100644 --- a/test/unit/lib/plugins/print.test.js +++ b/test/unit/lib/plugins/print.test.js @@ -10,11 +10,11 @@ const expect = chai.expect; describe('test/unit/lib/plugins/print.test.js', () => { it('correctly prints config', async () => { - const { stdoutData } = await runServerless({ + const { output } = await runServerless({ fixture: 'aws', command: 'print', }); - expect(stdoutData).to.include('name: aws'); + expect(output).to.include('name: aws'); }); }); diff --git a/test/unit/lib/utils/logDeprecation.test.js b/test/unit/lib/utils/logDeprecation.test.js index 013607d47..abfdf5022 100644 --- a/test/unit/lib/utils/logDeprecation.test.js +++ b/test/unit/lib/utils/logDeprecation.test.js @@ -3,17 +3,14 @@ const sandbox = require('sinon'); const expect = require('chai').expect; const overrideEnv = require('process-utils/override-env'); -const overrideStdoutWrite = require('process-utils/override-stdout-write'); -const runServerless = require('../../../utils/run-serverless'); const ServerlessError = require('../../../../lib/serverless-error'); describe('test/unit/lib/utils/logDeprecation.test.js', () => { let restoreEnv; - let originalEnv; beforeEach(() => { delete require.cache[require.resolve('../../../../lib/utils/logDeprecation')]; - ({ originalEnv, restoreEnv } = overrideEnv({ + ({ restoreEnv } = overrideEnv({ whitelist: ['APPDATA', 'HOME', 'PATH', 'TEMP', 'TMP', 'TMPDIR', 'USERPROFILE'], })); }); @@ -23,88 +20,6 @@ describe('test/unit/lib/utils/logDeprecation.test.js', () => { restoreEnv(); }); - it('should log deprecation message if not disabled and first time', () => { - const logDeprecation = require('../../../../lib/utils/logDeprecation'); - logDeprecation.defaultMode = 'warn'; - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => logDeprecation('CODE1', 'Start using deprecation log') - ); - expect(stdoutData).to.include('Start using deprecation log'); - - expect(stdoutData).to.include('Deprecation warning'); - expect(stdoutData).to.include('https://www.serverless.com/framework/docs/deprecations/#CODE1'); - }); - - it('should not log deprecation if disabled in env.SLS_DEPRECATION_DISABLE', () => { - process.env.SLS_DEPRECATION_DISABLE = 'CODE1'; - const logDeprecation = require('../../../../lib/utils/logDeprecation'); - - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => logDeprecation('CODE1', 'Start using deprecation log') - ); - expect(stdoutData).to.equal(''); - }); - - it('should not log deprecation if disabled in serviceConfig', () => { - // We need original process env variables for npm-conf - Object.assign(process.env, originalEnv); - const logDeprecation = require('../../../../lib/utils/logDeprecation'); - return runServerless({ - fixture: 'function', - configExt: { disabledDeprecations: ['CODE1'] }, - command: 'package', - }).then(({ serverless }) => { - const serviceConfig = serverless.service; - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => logDeprecation('CODE1', 'Start using deprecation log', { serviceConfig }) - ); - expect(stdoutData).to.equal(''); - }); - }); - - it('should not log deprecation if disabled by wildcard in env', () => { - process.env.SLS_DEPRECATION_DISABLE = '*'; - const logDeprecation = require('../../../../lib/utils/logDeprecation'); - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => logDeprecation('CODE1', 'Start using deprecation log') - ); - expect(stdoutData).to.equal(''); - }); - - it('should not log deprecation if disabled by wildcard in service config', () => { - Object.assign(process.env, originalEnv); - const logDeprecation = require('../../../../lib/utils/logDeprecation'); - return runServerless({ - fixture: 'function', - configExt: { disabledDeprecations: '*' }, - command: 'package', - }).then(({ serverless }) => { - const serviceConfig = serverless.service; - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => logDeprecation('CODE1', 'Start using deprecation log', { serviceConfig }) - ); - expect(stdoutData).to.equal(''); - }); - }); - - it('should throw on deprecation if env.SLS_DEPRECATION_NOTIFICATION_MODE=error', () => { - process.env.SLS_DEPRECATION_NOTIFICATION_MODE = 'error'; - const logDeprecation = require('../../../../lib/utils/logDeprecation'); - expect(() => logDeprecation('CODE1', 'Start using deprecation log')) - .to.throw(ServerlessError) - .with.property('code', 'REJECTED_DEPRECATION_CODE1'); - }); - it('should throw on deprecation if error notifications mode set in service config', () => { const logDeprecation = require('../../../../lib/utils/logDeprecation'); expect(() => @@ -115,54 +30,4 @@ describe('test/unit/lib/utils/logDeprecation.test.js', () => { .to.throw(ServerlessError) .with.property('code', 'REJECTED_DEPRECATION_CODE1'); }); - - it('should not log deprecation twice', () => { - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => { - const logDeprecation = require('../../../../lib/utils/logDeprecation'); - logDeprecation.defaultMode = 'warn'; - logDeprecation('CODE1', 'Start using deprecation log'); - expect(stdoutData).to.include('Start using deprecation log'); - stdoutData = ''; - logDeprecation('CODE1', 'Start using deprecation log'); - } - ); - expect(stdoutData).to.equal(''); - }); - - it('should expose working `flushBuffered` method', () => { - let stdoutData = ''; - overrideStdoutWrite( - (data) => (stdoutData += data), - () => { - const logDeprecation = require('../../../../lib/utils/logDeprecation'); - logDeprecation('CODE1', 'First deprecation'); - expect(stdoutData).to.not.include('First deprecation'); - logDeprecation('CODE2', 'Second deprecation'); - expect(stdoutData).to.not.include('Second deprecation'); - logDeprecation.flushBuffered(); - } - ); - expect(stdoutData).to.include('First deprecation'); - expect(stdoutData).to.include('Second deprecation'); - }); - - it('should expose working `printSummary` method', async () => { - let stdoutData = ''; - await overrideStdoutWrite( - (data) => (stdoutData += data), - async () => { - const logDeprecation = require('../../../../lib/utils/logDeprecation'); - logDeprecation('CODE1', 'First deprecation'); - expect(stdoutData).to.not.include('First deprecation'); - logDeprecation('CODE2', 'Second deprecation'); - expect(stdoutData).to.not.include('Second deprecation'); - await logDeprecation.printSummary(); - } - ); - expect(stdoutData).to.include('First deprecation'); - expect(stdoutData).to.include('Second deprecation'); - }); });