'use strict'; const chai = require('chai'); const sinon = require('sinon'); const path = require('path'); const fsp = require('fs').promises; const _ = require('lodash'); const log = require('log').get('serverless:test'); const runServerless = require('../../../utils/run-serverless'); // Configure chai chai.use(require('chai-as-promised')); const expect = require('chai').expect; const createApiStub = () => { const requests = []; return { requests, stub: sinon.stub().callsFake(async (pathname, { method } = { method: 'GET' }) => { log.debug('api request %s', pathname); if (pathname.includes('orgs/name/')) { requests.push('/orgs/name/{org}'); const orgName = pathname.split('/').filter(Boolean).pop(); return { orgId: `${orgName}id` }; } else if (pathname.includes('/org/')) { if (method.toUpperCase() === 'GET') { requests.push('get-token'); return { status: 'existing_token', token: { accessToken: 'accesss-token' }, }; } } else if (pathname.endsWith('/token')) { if (method.toUpperCase() === 'PATCH') { requests.push('activate-token'); return ''; } } else if (pathname.includes('/tokens?')) { if (method.toUpperCase() === 'DELETE') { requests.push( pathname.includes('token=') ? 'deactivate-other-tokens' : 'deactivate-all-tokens' ); return ''; } } else if (pathname.includes('/token?')) { if (method.toUpperCase() === 'DELETE') { requests.push('deactivate-token'); return ''; } } throw new Error(`Unexpected request: ${pathname}`); }), }; }; let serviceName = 'irrelevant'; const createAwsRequestStubMap = () => ({ CloudFormation: { describeStacks: { Stacks: [{ Outputs: [] }] }, describeStackResource: { StackResourceDetail: { PhysicalResourceId: 'deployment-bucket' }, }, }, Lambda: { getFunction: { Configuration: { LastModified: '2020-05-20T15:34:16.494+0000', }, }, }, S3: { headObject: async ({ Key: s3Key }) => { if (s3Key.includes('sls-otel.')) { throw Object.assign(new Error('Not found'), { code: 'AWS_S3_HEAD_OBJECT_NOT_FOUND', }); } return { Metadata: { filesha256: 'RRYyTm4Ri8mocpvx44pvas4JKLYtdJS3Z8MOlrZrDXA=' }, }; }, listObjectsV2: () => ({ Contents: [ { Key: `serverless/${serviceName}/dev/1589988704359-2020-05-20T15:31:44.359Z/artifact.zip`, LastModified: new Date(), ETag: '"5102a4cf710cae6497dba9e61b85d0a4"', Size: 356, StorageClass: 'STANDARD', }, { Key: `serverless/${serviceName}/dev/1589988704359-2020-05-20T15:31:44.359Z/compiled-cloudformation-template.json`, LastModified: new Date(), ETag: '"5102a4cf710cae6497dba9e61b85d0a4"', Size: 356, StorageClass: 'STANDARD', }, { Key: `serverless/${serviceName}/dev/1589988704359-2020-05-20T15:31:44.359Z/serverless-state.json`, LastModified: new Date(), ETag: '"5102a4cf710cae6497dba9e61b85d0a4"', Size: 356, StorageClass: 'STANDARD', }, ], }), headBucket: {}, upload: sinon.stub().callsFake(async ({ Body: body }) => { if (typeof body.destroy === 'function') { // Ensure to drain eventual file streams, otherwise file remain locked and // on Windows they cannot be removed, resulting with homedir being dirty for next test runs await new Promise((resolve, reject) => { body.on('data', () => {}); body.on('end', resolve); body.on('error', reject); }); } return {}; }), }, STS: { getCallerIdentity: { ResponseMetadata: { RequestId: 'ffffffff-ffff-ffff-ffff-ffffffffffff' }, UserId: 'XXXXXXXXXXXXXXXXXXXXX', Account: '999999999999', Arn: 'arn:aws:iam::999999999999:user/test', }, }, }); describe('test/unit/lib/classes/console.test.js', () => { describe('enabled', () => { describe('deploy', () => { let serverless; let servicePath; let cfTemplate; let awsNaming; let uploadStub; let apiStub; let otelIngenstionRequests; before(async () => { const awsRequestStubMap = createAwsRequestStubMap(); uploadStub = awsRequestStubMap.S3.upload; ({ requests: otelIngenstionRequests, stub: apiStub } = createApiStub()); ({ serverless, cfTemplate, awsNaming, fixtureData: { servicePath }, } = await runServerless({ fixture: 'packaging', command: 'deploy', lastLifecycleHookName: 'aws:deploy:deploy:uploadArtifacts', configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: apiStub, }, awsRequestStubMap, })); }); it('should setup needed environment variables on supported functions', () => { const fnVariablesList = [ cfTemplate.Resources[awsNaming.getLambdaLogicalId('fnService')].Properties.Environment .Variables, cfTemplate.Resources[awsNaming.getLambdaLogicalId('fnIndividual')].Properties.Environment .Variables, ]; for (const fnVariables of fnVariablesList) { expect(fnVariables).to.have.property('SLS_OTEL_REPORT_REQUEST_HEADERS'); expect(fnVariables).to.have.property('SLS_OTEL_REPORT_METRICS_URL'); expect(fnVariables).to.have.property('SLS_OTEL_REPORT_LOGS_URL'); expect(fnVariables).to.have.property('AWS_LAMBDA_EXEC_WRAPPER'); } const notSupportedFnVariables = _.get( cfTemplate.Resources[awsNaming.getLambdaLogicalId('fnGo')].Properties, 'Environment.Variables', {} ); expect(notSupportedFnVariables).to.not.have.property('SLS_OTEL_REPORT_REQUEST_HEADERS'); expect(notSupportedFnVariables).to.not.have.property('SLS_OTEL_REPORT_METRICS_URL'); expect(notSupportedFnVariables).to.not.have.property('AWS_LAMBDA_EXEC_WRAPPER'); }); it('should reflect default userSettings', () => { const userSettingsString = cfTemplate.Resources[awsNaming.getLambdaLogicalId('fnService')].Properties.Environment .Variables.SLS_OTEL_USER_SETTINGS; if (!userSettingsString) return; expect(JSON.parse(userSettingsString)).to.deep.equal({}); }); it('should package extension layer', async () => { expect(cfTemplate.Resources).to.have.property( awsNaming.getConsoleExtensionLayerLogicalId() ); await fsp.access( path.resolve( servicePath, '.serverless', await serverless.console.deferredExtensionLayerBasename ) ); }); it('should upload extension layer to S3', async () => { const consoleExtensionLayerBasename = await serverless.console .deferredExtensionLayerBasename; log.debug( 'layer basename: %s, s3Keys: %o', consoleExtensionLayerBasename, uploadStub.args.map(([{ Key: s3Key }]) => s3Key) ); const uploadArgs = uploadStub.args.find(([{ Key: s3Key }]) => s3Key.endsWith(consoleExtensionLayerBasename) )[0]; // Confirm that Bucket is properly resolved in outer `uploadZipFile` method expect(typeof uploadArgs.Bucket).to.equal('string'); }); it('should activate otel ingestion token', () => { otelIngenstionRequests.includes('activate-token'); }); }); describe('package', () => { let userSettings; before(async () => { const { stub: apiStub } = createApiStub(); const { cfTemplate, awsNaming } = await runServerless({ fixture: 'function', command: 'package', configExt: { console: { disableLogsCollection: true, disableRequestResponseCollection: true, }, org: 'testorg', }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: apiStub, }, }); userSettings = JSON.parse( cfTemplate.Resources[awsNaming.getLambdaLogicalId('basic')].Properties.Environment .Variables.SLS_OTEL_USER_SETTINGS ); }); it('should propagate `disableLogsCollection`', () => { expect(userSettings.disableLogsMonitoring).to.be.true; }); it('should propagate `disableRequestResponseCollection`', () => { expect(userSettings.disableRequestResponseMonitoring).to.be.true; }); }); describe('package with "provider.layers" configuration', () => { it('should setup console wihout errors', async () => { const { cfTemplate, awsNaming } = await runServerless({ fixture: 'function-layers', command: 'package', configExt: { console: true, org: 'testorg', layers: { extra1: { path: 'test-layer' }, extra2: { path: 'test-layer' }, }, package: { individually: true, }, provider: { layers: [{ Ref: 'Extra1LambdaLayer' }, { Ref: 'Extra2LambdaLayer' }] }, functions: { layerFunc: { layers: null }, capitalLayerFunc: { layers: null } }, }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: createAwsRequestStubMap(), env: { SLS_ORG_TOKEN: 'dummy' }, }); const configuredLayers = cfTemplate.Resources[awsNaming.getLambdaLogicalId('layerFunc')].Properties.Layers; expect( configuredLayers.some( ({ Ref: logicalId }) => logicalId === awsNaming.getConsoleExtensionLayerLogicalId() ) ).to.be.true; }); }); describe('disable logs collection', () => { it('should not setup report logs url', async () => { const { cfTemplate, awsNaming } = await runServerless({ fixture: 'function', command: 'package', configExt: { console: { disableLogsCollection: true }, org: 'testorg', }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: createAwsRequestStubMap(), env: { SLS_ORG_TOKEN: 'dummy' }, }); const fnVariables = cfTemplate.Resources[awsNaming.getLambdaLogicalId('basic')].Properties.Environment .Variables; expect(fnVariables).to.have.property('SLS_OTEL_REPORT_REQUEST_HEADERS'); expect(fnVariables).to.not.have.property('SLS_OTEL_REPORT_LOGS_URL'); expect(fnVariables).to.have.property('AWS_LAMBDA_EXEC_WRAPPER'); }); }); describe('package for custom deployment bucket', () => { let cfTemplate; let awsNaming; before(async () => { ({ cfTemplate, awsNaming } = await runServerless({ fixture: 'function', command: 'package', configExt: { console: true, org: 'testorg', provider: { deploymentBucket: 'custom' } }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, })); }); it('should not reference default deployment bucket anywhere', () => { expect(JSON.stringify(cfTemplate.Resources)).to.not.contain('ServerlessDeploymentBucket'); }); it('should reference custom S3 bucket at layer version', () => { expect( cfTemplate.Resources[awsNaming.getConsoleExtensionLayerLogicalId()].Properties.Content .S3Bucket ).to.equal('custom'); }); }); }); describe('deploy --package', () => { let consolePackage; let consoleDeploy; let servicePath; let uploadStub; let apiStub; let otelIngenstionRequests; before(async () => { ({ requests: otelIngenstionRequests, stub: apiStub } = createApiStub()); ({ serverless: { console: consolePackage }, fixtureData: { servicePath }, } = await runServerless({ fixture: 'function', command: 'package', options: { package: 'package-dir' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: apiStub, }, })); const awsRequestStubMap = createAwsRequestStubMap(); uploadStub = awsRequestStubMap.S3.upload; ({ serverless: { console: consoleDeploy }, } = await runServerless({ cwd: servicePath, command: 'deploy', lastLifecycleHookName: 'aws:deploy:deploy:uploadArtifacts', options: { package: 'package-dir' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: apiStub, }, awsRequestStubMap, })); }); it('should use service id as stored in the state', () => { expect(consoleDeploy.serviceId).to.equal(consolePackage.serviceId); }); it('should upload extension layer to S3', async () => { const extensionLayerFilename = await consoleDeploy.deferredExtensionLayerBasename; expect(uploadStub.args.some(([{ Key: s3Key }]) => s3Key.endsWith(extensionLayerFilename))).to .be.true; }); it('should activate otel ingestion token', () => { otelIngenstionRequests.includes('activate-token'); }); }); describe('deploy function', () => { let serverless; let uploadStub; let updateFunctionStub; let publishLayerStub; let apiStub; let otelIngenstionRequests; before(async () => { updateFunctionStub = sinon.stub().resolves({}); publishLayerStub = sinon.stub().resolves({}); const awsRequestStubMap = createAwsRequestStubMap(); uploadStub = awsRequestStubMap.S3.upload; let isFirstLayerVersionsQuery = true; ({ requests: otelIngenstionRequests, stub: apiStub } = createApiStub()); ({ serverless } = await runServerless({ fixture: 'function', command: 'deploy function', options: { function: 'basic' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: apiStub, }, awsRequestStubMap: { ...awsRequestStubMap, Lambda: { getFunction: { Configuration: { State: 'Active', LastUpdateStatus: 'Successful' } }, listLayerVersions() { if (isFirstLayerVersionsQuery) { isFirstLayerVersionsQuery = false; return { LayerVersions: [] }; } return { LayerVersions: [{ LayerVersionArn: 'extension-arn' }] }; }, publishLayerVersion: publishLayerStub, updateFunctionConfiguration: updateFunctionStub, updateFunctionCode: {}, }, }, })); }); it('should setup needed environment variables', () => { const fnVariables = updateFunctionStub.args[0][0].Environment.Variables; expect(fnVariables).to.have.property('SLS_OTEL_REPORT_REQUEST_HEADERS'); expect(fnVariables).to.have.property('SLS_OTEL_REPORT_METRICS_URL'); expect(fnVariables).to.have.property('AWS_LAMBDA_EXEC_WRAPPER'); }); it('should upload extension layer to S3', async () => { const extensionLayerFilename = await serverless.console.deferredExtensionLayerBasename; expect(uploadStub.args.some(([{ Key: s3Key }]) => s3Key.endsWith(extensionLayerFilename))).to .be.true; }); it('should activate otel ingestion token', () => { otelIngenstionRequests.includes('activate-token'); }); }); describe('rollback', () => { let slsConsole; let apiStub; let otelIngenstionRequests; before(async () => { const awsRequestStubMap = createAwsRequestStubMap(); ({ requests: otelIngenstionRequests, stub: apiStub } = createApiStub()); ({ serverless: { console: slsConsole }, } = await runServerless({ fixture: 'function', command: 'rollback', options: { timestamp: '2020-05-20T15:31:44.359Z' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: apiStub, }, awsRequestStubMap: { ...awsRequestStubMap, S3: { ...awsRequestStubMap.S3, getObject: async ({ Key: s3Key }) => { if (s3Key.endsWith('/serverless-state.json')) { return { Body: JSON.stringify({ console: { schemaVersion: '1', otelIngestionToken: 'rollback-token', service: 'test-console', stage: 'dev', orgId: 'testorgid', }, }), }; } throw new Error(`Unexpected request: ${s3Key}`); }, }, CloudFormation: { ...awsRequestStubMap.CloudFormation, deleteChangeSet: {}, createChangeSet: {}, describeChangeSet: { Status: 'CREATE_COMPLETE', }, executeChangeSet: {}, describeStackEvents: { StackEvents: [ { EventId: '1', ResourceType: 'AWS::CloudFormation::Stack', ResourceStatus: 'UPDATE_COMPLETE', }, ], }, }, }, hooks: { beforeInstanceRun: (serverless) => { serviceName = serverless.service.service; }, }, })); }); it('should resolve otel ingestion token from the state', async () => { expect(await slsConsole.deferredOtelIngestionToken).to.equal('rollback-token'); }); it('should activate otel ingestion token', () => { otelIngenstionRequests.includes('activate-token'); }); }); describe('remove', () => { let otelIngenstionRequests; let apiStub; before(async () => { const awsRequestStubMap = createAwsRequestStubMap(); ({ requests: otelIngenstionRequests, stub: apiStub } = createApiStub()); await runServerless({ fixture: 'function', command: 'remove', configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: apiStub, }, awsRequestStubMap: { ...awsRequestStubMap, CloudFormation: { ...awsRequestStubMap.CloudFormation, deleteStack: {}, describeStackEvents: { StackEvents: [ { EventId: '1', ResourceType: 'AWS::CloudFormation::Stack', ResourceStatus: 'DELETE_COMPLETE', }, ], }, }, ECR: { async describeRepositories() { throw Object.assign(new Error('RepositoryNotFoundException'), { providerError: { code: 'RepositoryNotFoundException' }, }); }, }, S3: { ...awsRequestStubMap.S3, deleteObjects: {}, }, }, }); }); it('should deactivate all ingestion tokens', () => { otelIngenstionRequests.includes('deactivate-all-token'); }); }); it('should support "console.org"', async () => { const { requests: otelIngenstionRequests, stub: apiStub } = createApiStub(); await runServerless({ fixture: 'function', command: 'package', configExt: { console: { org: 'testorg', }, org: 'ignore', }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: apiStub, }, }); for (const [url] of apiStub.args) expect(url).to.not.include('/ignoreid/'); otelIngenstionRequests.includes('activate-token'); }); describe('errors', () => { it('should abort when console enabled but not authenticated', async () => { await expect( runServerless({ fixture: 'function', command: 'package', configExt: { console: true, org: 'testorg' }, }) ).to.eventually.be.rejected.and.have.property('code', 'CONSOLE_NOT_AUTHENTICATED'); }); it('should abort when function has already maximum numbers of layers configured', async () => { await expect( runServerless({ fixture: 'function-layers', command: 'package', configExt: { console: true, org: 'testorg', layers: { extra1: { path: 'test-layer' }, extra2: { path: 'test-layer' }, }, functions: { layerFuncWithConfig: { layers: [ { Ref: 'TestLayerLambdaLayer' }, { Ref: 'TestLayerWithCapitalsLambdaLayer' }, { Ref: 'TestLayerWithNoNameLambdaLayer' }, { Ref: 'Extra1LambdaLayer' }, { Ref: 'Extra2LambdaLayer' }, ], }, }, }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: createAwsRequestStubMap(), env: { SLS_ORG_TOKEN: 'dummy' }, }) ).to.eventually.be.rejected.and.have.property('code', 'TOO_MANY_LAYERS_TO_SETUP_CONSOLE'); }); it( 'should throw integration error when attempting to deploy package, ' + 'packaged with different console integration version', async () => { const { fixtureData: { servicePath }, } = await runServerless({ fixture: 'function', command: 'package', options: { package: 'package-dir' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: createAwsRequestStubMap(), }); const stateFilename = path.resolve(servicePath, 'package-dir', 'serverless-state.json'); const state = JSON.parse(await fsp.readFile(stateFilename, 'utf-8')); state.console.schemaVersion = 'other'; await fsp.writeFile(stateFilename, JSON.stringify(state)); await expect( runServerless({ cwd: servicePath, command: 'deploy', lastLifecycleHookName: 'aws:deploy:deploy:uploadArtifacts', options: { package: 'package-dir' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: createAwsRequestStubMap(), }) ).to.eventually.be.rejected.and.have.property('code', 'CONSOLE_INTEGRATION_MISMATCH'); } ); it( 'should throw mismatch error when attempting to deploy package, ' + 'packaged with different org', async () => { const { fixtureData: { servicePath, updateConfig }, } = await runServerless({ fixture: 'function', command: 'package', options: { package: 'package-dir' }, configExt: { console: true, org: 'other' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, }); await updateConfig({ org: 'testorg' }); await expect( runServerless({ cwd: servicePath, command: 'deploy', lastLifecycleHookName: 'aws:deploy:deploy:uploadArtifacts', options: { package: 'package-dir' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: createAwsRequestStubMap(), }) ).to.eventually.be.rejected.and.have.property('code', 'CONSOLE_ORG_MISMATCH'); } ); it( 'should throw mismatch error when attempting to deploy package, ' + 'packaged with different region', async () => { const { fixtureData: { servicePath, updateConfig }, } = await runServerless({ fixture: 'function', command: 'package', options: { package: 'package-dir' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, }); await updateConfig({ provider: { region: 'us-east-2' } }); await expect( runServerless({ cwd: servicePath, command: 'deploy', lastLifecycleHookName: 'aws:deploy:deploy:uploadArtifacts', options: { package: 'package-dir' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: createAwsRequestStubMap(), }) ).to.eventually.be.rejected.and.have.property('code', 'CONSOLE_REGION_MISMATCH'); } ); it( 'should throw activation mismatch error when attempting to deploy with ' + 'console integration off, but packaged with console integration on', async () => { const { fixtureData: { servicePath, updateConfig }, } = await runServerless({ fixture: 'function', command: 'package', options: { package: 'package-dir' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, }); const stateFilename = path.resolve(servicePath, 'package-dir', 'serverless-state.json'); const state = JSON.parse(await fsp.readFile(stateFilename, 'utf-8')); state.console.orgId = 'other'; await fsp.writeFile(stateFilename, JSON.stringify(state)); await updateConfig({ org: null, console: null }); await expect( runServerless({ cwd: servicePath, command: 'deploy', lastLifecycleHookName: 'aws:deploy:deploy:uploadArtifacts', options: { package: 'package-dir' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: createAwsRequestStubMap(), }) ).to.eventually.be.rejected.and.have.property('code', 'CONSOLE_ACTIVATION_MISMATCH'); } ); it( 'should throw integration error when attempting to rollback deployment, ' + 'to one deployed with different console integration version', async () => { const awsRequestStubMap = createAwsRequestStubMap(); await expect( runServerless({ fixture: 'function', command: 'rollback', lastLifecycleHookName: 'aws:deploy:deploy:uploadArtifacts', options: { timestamp: '2020-05-20T15:31:44.359Z' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: { ...awsRequestStubMap, S3: { ...awsRequestStubMap.S3, getObject: async ({ Key: s3Key }) => { if (s3Key.endsWith('/serverless-state.json')) { return { Body: JSON.stringify({ console: { schemaVersion: '2', otelIngestionToken: 'rollback-token', service: 'test-console', stage: 'dev', orgId: 'testorgid', }, }), }; } throw new Error(`Unexpected request: ${s3Key}`); }, }, CloudFormation: { ...awsRequestStubMap.CloudFormation, deleteChangeSet: {}, createChangeSet: {}, describeChangeSet: { Status: 'CREATE_COMPLETE', }, executeChangeSet: {}, describeStackEvents: { StackEvents: [ { EventId: '1', ResourceType: 'AWS::CloudFormation::Stack', ResourceStatus: 'UPDATE_COMPLETE', }, ], }, }, }, hooks: { beforeInstanceRun: (serverless) => { serviceName = serverless.service.service; }, }, }) ).to.eventually.be.rejected.and.have.property( 'code', 'CONSOLE_INTEGRATION_MISMATCH_ROLLBACK' ); } ); it( 'should throw integration error when attempting to rollback deployment, ' + 'to one deployed with different org', async () => { const awsRequestStubMap = createAwsRequestStubMap(); await expect( runServerless({ fixture: 'function', command: 'rollback', lastLifecycleHookName: 'aws:deploy:deploy:uploadArtifacts', options: { timestamp: '2020-05-20T15:31:44.359Z' }, configExt: { console: true, org: 'testorg' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: { ...awsRequestStubMap, S3: { ...awsRequestStubMap.S3, getObject: async ({ Key: s3Key }) => { if (s3Key.endsWith('/serverless-state.json')) { return { Body: JSON.stringify({ console: { schemaVersion: '1', otelIngestionToken: 'rollback-token', service: 'test-console', stage: 'dev', orgId: 'othertestorgid', }, }), }; } throw new Error(`Unexpected request: ${s3Key}`); }, }, CloudFormation: { ...awsRequestStubMap.CloudFormation, deleteChangeSet: {}, createChangeSet: {}, describeChangeSet: { Status: 'CREATE_COMPLETE', }, executeChangeSet: {}, describeStackEvents: { StackEvents: [ { EventId: '1', ResourceType: 'AWS::CloudFormation::Stack', ResourceStatus: 'UPDATE_COMPLETE', }, ], }, }, }, hooks: { beforeInstanceRun: (serverless) => { serviceName = serverless.service.service; }, }, }) ).to.eventually.be.rejected.and.have.property('code', 'CONSOLE_ORG_MISMATCH_ROLLBACK'); } ); it( 'should throw integration error when attempting to rollback deployment, ' + 'deployed with console, while having console disabled', async () => { const awsRequestStubMap = createAwsRequestStubMap(); await expect( runServerless({ fixture: 'function', command: 'rollback', lastLifecycleHookName: 'aws:deploy:deploy:uploadArtifacts', options: { timestamp: '2020-05-20T15:31:44.359Z' }, env: { SLS_ORG_TOKEN: 'dummy' }, modulesCacheStub: { [require.resolve('@serverless/utils/api-request')]: createApiStub().stub, }, awsRequestStubMap: { ...awsRequestStubMap, S3: { ...awsRequestStubMap.S3, getObject: async ({ Key: s3Key }) => { if (s3Key.endsWith('/serverless-state.json')) { return { Body: JSON.stringify({ console: { schemaVersion: '1', otelIngestionToken: 'rollback-token', service: 'test-console', stage: 'dev', orgId: 'othertestorgid', }, }), }; } throw new Error(`Unexpected request: ${s3Key}`); }, }, CloudFormation: { ...awsRequestStubMap.CloudFormation, deleteChangeSet: {}, createChangeSet: {}, describeChangeSet: { Status: 'CREATE_COMPLETE', }, executeChangeSet: {}, describeStackEvents: { StackEvents: [ { EventId: '1', ResourceType: 'AWS::CloudFormation::Stack', ResourceStatus: 'UPDATE_COMPLETE', }, ], }, }, }, hooks: { beforeInstanceRun: (serverless) => { serviceName = serverless.service.service; }, }, }) ).to.eventually.be.rejected.and.have.property( 'code', 'CONSOLE_ACTIVATION_MISMATCH_ROLLBACK' ); } ); }); describe('disabled', () => { it('should not enable console when no `console: true`', async () => { const { serverless } = await runServerless({ fixture: 'function', command: 'package', configExt: { org: 'testorg' }, }); expect(serverless.console.isEnabled).to.be.false; }); it('should not enable console when not supported command', async () => { const { serverless } = await runServerless({ fixture: 'function', command: 'print', configExt: { console: true, org: 'testorg' }, }); expect(serverless.console.isEnabled).to.be.false; }); it('should not enable when no supported functions', async () => { const { serverless } = await runServerless({ fixture: 'aws', command: 'package', configExt: { console: true, org: 'testorg' }, }); expect(serverless.console.isEnabled).to.be.false; }); }); });