serverless/test/unit/lib/Serverless.test.js
2021-07-15 10:19:42 +02:00

380 lines
13 KiB
JavaScript

'use strict';
const chai = require('chai');
chai.use(require('chai-as-promised'));
const { expect } = chai;
const Serverless = require('../../../lib/Serverless');
const semverRegex = require('semver-regex');
const path = require('path');
const sinon = require('sinon');
const BbPromise = require('bluebird');
const YamlParser = require('../../../lib/classes/YamlParser');
const PluginManager = require('../../../lib/classes/PluginManager');
const Utils = require('../../../lib/classes/Utils');
const Service = require('../../../lib/classes/Service');
const ConfigSchemaHandler = require('../../../lib/classes/ConfigSchemaHandler');
const CLI = require('../../../lib/classes/CLI');
const ServerlessError = require('../../../lib/serverless-error');
const conditionallyLoadDotenv = require('../../../lib/cli/conditionally-load-dotenv');
const runServerless = require('../../utils/run-serverless');
const fixtures = require('../../fixtures/programmatic');
const fs = require('fs');
describe('Serverless', () => {
let serverless;
beforeEach(() => {
serverless = new Serverless({ commands: ['print'], options: {}, serviceDir: null });
});
describe('#constructor()', () => {
it('should set the correct config if a config object is passed', () => {
const configObj = { some: 'config' };
const serverlessWithConfig = new Serverless(configObj);
expect(serverlessWithConfig.config.some).to.equal('config');
});
it('should set an empty config object if no config object passed', () => {
expect(Object.keys(serverless.config)).to.include('serverless');
expect(Object.keys(serverless.config)).to.include('serverlessPath');
});
it('should set an empty providers object', () => {
expect(serverless.providers).to.deep.equal({});
});
it('should set the Serverless version', () => {
expect(serverless.version.length).to.be.at.least(1);
});
it('should set the YamlParser class instance', () => {
expect(serverless.yamlParser).to.be.instanceof(YamlParser);
});
it('should set the PluginManager class instance', () => {
expect(serverless.pluginManager).to.be.instanceof(PluginManager);
});
it('should set the Utils class instance', () => {
expect(serverless.utils).to.be.instanceof(Utils);
});
it('should set the Service class instance', () => {
expect(serverless.service).to.be.instanceof(Service);
});
it('should set the ConfigSchemaHandler class instance', () => {
expect(serverless.configSchemaHandler).to.be.instanceof(ConfigSchemaHandler);
});
it('should have a config object', () => {
expect(serverless.config).to.not.equal(undefined);
});
it('should have a classes object', () => {
expect(serverless.classes).to.not.equal(undefined);
});
it('should store the CLI class inside the classes object', () => {
expect(serverless.classes.CLI).to.deep.equal(CLI);
});
it('should store the YamlParser class inside the classes object', () => {
expect(serverless.classes.YamlParser).to.deep.equal(YamlParser);
});
it('should store the PluginManager class inside the classes object', () => {
expect(serverless.classes.PluginManager).to.deep.equal(PluginManager);
});
it('should store the Utils class inside the classes object', () => {
expect(serverless.classes.Utils).to.deep.equal(Utils);
});
it('should store the Service class inside the classes object', () => {
expect(serverless.classes.Service).to.deep.equal(Service);
});
it('should store the ConfigSchemaHandler class inside the classes object', () => {
expect(serverless.classes.ConfigSchemaHandler).to.deep.equal(ConfigSchemaHandler);
});
it('should store the Error class inside the classes object', () => {
expect(serverless.classes.Error).to.deep.equal(ServerlessError);
});
});
describe('#init()', () => {
it('should set an instanceId', () =>
serverless.init().then(() => {
expect(serverless.instanceId).to.match(/\d/);
}));
it('should create a new CLI instance', () =>
serverless.init().then(() => {
expect(serverless.cli).to.be.instanceof(CLI);
}));
it('should allow a custom CLI instance', () => {
class CustomCLI extends CLI {}
serverless.classes.CLI = CustomCLI;
return serverless.init().then(() => {
expect(serverless.cli).to.be.instanceof(CLI);
expect(serverless.cli.constructor.name).to.equal('CustomCLI');
});
});
// note: we just test that the processedInput variable is set (not the content of it)
// the test for the correct input is done in the CLI class test file
it('should receive the processed input form the CLI instance', () =>
serverless.init().then(() => {
expect(serverless.processedInput).to.not.deep.equal({});
}));
});
describe('#run()', () => {
let displayHelpStub;
let validateCommandStub;
let populateServiceStub;
let runStub;
beforeEach(() => {
serverless.cli = new CLI(serverless);
serverless.processedInput = { commands: [], options: {} };
// setup default stubs
displayHelpStub = sinon.stub(serverless.cli, 'displayHelp').returns(false);
validateCommandStub = sinon.stub(serverless.pluginManager, 'validateCommand').returns();
populateServiceStub = sinon.stub(serverless.variables, 'populateService').resolves();
runStub = sinon.stub(serverless.pluginManager, 'run').resolves();
});
afterEach(() => {
serverless.cli.displayHelp.restore();
serverless.pluginManager.validateCommand.restore();
serverless.variables.populateService.restore();
serverless.pluginManager.run.restore();
});
it('should resolve if the stats logging call throws an error / is rejected', () => {
return serverless.run().then(() => {
expect(displayHelpStub.calledOnce).to.equal(true);
expect(validateCommandStub.calledOnce).to.equal(true);
expect(populateServiceStub.calledOnce).to.equal(true);
expect(runStub.calledOnce).to.equal(true);
return BbPromise.resolve();
});
});
it('should resolve if help is displayed or no commands are entered', () => {
// overwrite displayHelpStub
displayHelpStub.returns(true);
return serverless.run().then(() => {
expect(displayHelpStub.calledOnce).to.equal(true);
expect(validateCommandStub.calledOnce).to.equal(false);
expect(populateServiceStub.calledOnce).to.equal(false);
expect(runStub.calledOnce).to.equal(false);
});
});
it('should run all the needed functions', () =>
serverless.run().then(() => {
expect(displayHelpStub.calledOnce).to.equal(true);
expect(validateCommandStub.calledOnce).to.equal(true);
expect(populateServiceStub.calledOnce).to.equal(true);
expect(runStub.calledOnce).to.equal(true);
}));
});
describe('#setProvider()', () => {
class ProviderMock {}
it('should set the provider object in the provider object', () => {
const myProvider = new ProviderMock();
serverless.setProvider('myProvider', myProvider);
expect(serverless.providers.myProvider).to.equal(myProvider);
});
});
describe('#getProvider()', () => {
class ProviderMock {}
let myProvider;
beforeEach(() => {
myProvider = new ProviderMock();
serverless.setProvider('myProvider', myProvider);
});
it('should return the provider object', () => {
const retrivedProvider = serverless.getProvider('myProvider');
expect(retrivedProvider).to.deep.equal(myProvider);
});
});
describe('#getVersion()', () => {
it('should get the correct Serverless version', () => {
expect(semverRegex().test(serverless.getVersion())).to.equal(true);
});
});
});
describe('test/unit/lib/Serverless.test.js', () => {
describe('When local version available', () => {
describe('When running global version', () => {
it('Should fallback to local version when it is found and "enableLocalInstallationFallback" is not set', () =>
runServerless({
fixture: 'locallyInstalledServerless',
command: 'print',
modulesCacheStub: {},
}).then(({ serverless }) => {
expect(Array.from(serverless.triggeredDeprecations)).to.deep.equal([]);
expect(serverless._isInvokedByGlobalInstallation).to.be.true;
expect(serverless.isLocallyInstalled).to.be.true;
expect(serverless.isLocalStub).to.be.true;
}));
it('Should report deprecation notice when "enableLocalInstallationFallback" is set', async () =>
expect(
runServerless({
fixture: 'locallyInstalledServerless',
configExt: { enableLocalInstallationFallback: false },
command: 'print',
modulesCacheStub: {},
})
).to.eventually.be.rejected.and.have.property(
'code',
'REJECTED_DEPRECATION_DISABLE_LOCAL_INSTALLATION_FALLBACK_SETTING'
));
it('Should not fallback to local when "enableLocalInstallationFallback" set to false', async () => {
const { serverless } = await runServerless({
fixture: 'locallyInstalledServerless',
configExt: {
disabledDeprecations: 'DISABLE_LOCAL_INSTALLATION_FALLBACK_SETTING',
enableLocalInstallationFallback: false,
},
command: 'print',
modulesCacheStub: {},
});
expect(serverless.invokedInstance).to.not.exist;
});
it('Should fallback to local version when "enableLocalInstallationFallback" set to true', () =>
runServerless({
fixture: 'locallyInstalledServerless',
configExt: {
disabledDeprecations: 'DISABLE_LOCAL_INSTALLATION_FALLBACK_SETTING',
enableLocalInstallationFallback: true,
},
command: 'print',
modulesCacheStub: {},
}).then(({ serverless }) => {
expect(Array.from(serverless.triggeredDeprecations)).to.deep.equal([
'DISABLE_LOCAL_INSTALLATION_FALLBACK_SETTING',
]);
expect(serverless._isInvokedByGlobalInstallation).to.be.true;
expect(serverless.isLocallyInstalled).to.be.true;
expect(serverless.isLocalStub).to.be.true;
}));
});
describe('When running local version', () => {
it('Should run without notice', () =>
fixtures.setup('locallyInstalledServerless').then(({ servicePath: serviceDir }) =>
runServerless({
serverlessDir: path.resolve(serviceDir, 'node_modules/serverless'),
cwd: serviceDir,
command: 'print',
}).then(({ serverless }) => {
expect(Array.from(serverless.triggeredDeprecations)).to.not.include(
'DISABLE_LOCAL_INSTALLATION_FALLBACK_SETTING'
);
expect(serverless._isInvokedByGlobalInstallation).to.be.false;
expect(serverless.isLocallyInstalled).to.be.true;
expect(serverless.isLocalStub).to.be.true;
})
));
});
});
describe('When local version not available', () => {
it('Should run without notice', () =>
runServerless({ fixture: 'aws', command: 'print', modulesCacheStub: {} }).then(
({ serverless }) => {
expect(Array.from(serverless.triggeredDeprecations)).to.deep.equal([]);
expect(serverless._isInvokedByGlobalInstallation).to.be.false;
expect(serverless.isLocallyInstalled).to.be.false;
expect(serverless.isLocalStub).to.not.exist;
}
));
});
describe('When .env file is available', () => {
let serviceDir;
before(async () => {
serviceDir = (
await fixtures.setup('function', {
configExt: {
useDotenv: true,
custom: {
fromDefaultEnv: '${env:DEFAULT_ENV_VARIABLE}',
fromStageEnv: "${env:STAGE_ENV_VARIABLE, 'not-found'}",
fromDefaultExpandedEnv: '${env:DEFAULT_ENV_VARIABLE_EXPANDED}',
},
},
})
).servicePath;
const defaultFileContent = `
DEFAULT_ENV_VARIABLE=valuefromdefault
DEFAULT_ENV_VARIABLE_EXPANDED=$DEFAULT_ENV_VARIABLE/expanded
`;
await fs.promises.writeFile(path.join(serviceDir, '.env'), defaultFileContent);
conditionallyLoadDotenv.clear();
});
it('Should load environment variables from default .env file if no matching stage', async () => {
const result = await runServerless({
cwd: serviceDir,
command: 'package',
shouldUseLegacyVariablesResolver: true,
});
expect(result.serverless.service.custom.fromDefaultEnv).to.equal('valuefromdefault');
expect(result.serverless.service.custom.fromDefaultExpandedEnv).to.equal(
'valuefromdefault/expanded'
);
expect(result.serverless.service.custom.fromStageEnv).to.equal('not-found');
});
});
describe('Legacy API interface', () => {
let serverless;
before(async () => {
({ serverless } = await runServerless({
fixture: 'aws',
command: 'package',
}));
});
it('Ensure that instance is setup', async () => {
expect(serverless.variables).to.have.property('variableSyntax');
});
it('Ensure config.servicePath', async () => {
expect(serverless.config).to.have.property('servicePath');
});
});
});