'use strict' const expect = require('chai').expect const sinon = require('sinon') const AwsProvider = require('../../../../../lib/plugins/aws/provider') const AwsMetrics = require('../../../../../lib/plugins/aws/metrics') const Serverless = require('../../../../../lib/serverless') const CLI = require('../../../../../lib/classes/cli') const dayjs = require('dayjs') const LocalizedFormat = require('dayjs/plugin/localizedFormat') dayjs.extend(LocalizedFormat) describe('AwsMetrics', () => { let awsMetrics let serverless beforeEach(() => { serverless = new Serverless({ commands: [], options: {} }) serverless.cli = new CLI(serverless) const options = { stage: 'dev', region: 'us-east-1', } serverless.setProvider('aws', new AwsProvider(serverless, options)) awsMetrics = new AwsMetrics(serverless, options) }) describe('#constructor()', () => { it('should set the serverless instance to this.serverless', () => { expect(awsMetrics.serverless).to.deep.equal(serverless) }) it('should set the passed in options to this.options', () => { expect(awsMetrics.options).to.deep.equal({ stage: 'dev', region: 'us-east-1', }) }) it('should set the provider variable to the AwsProvider instance', () => expect(awsMetrics.provider).to.be.instanceof(AwsProvider)) it('should have a "metrics:metrics" hook', () => { // eslint-disable-next-line no-unused-expressions expect(awsMetrics.hooks['metrics:metrics']).to.not.be.undefined }) it('should run promise chain in order for "metrics:metrics" hook', async () => { const extendedValidateStub = sinon .stub(awsMetrics, 'extendedValidate') .resolves() const getMetricsStub = sinon.stub(awsMetrics, 'getMetrics').resolves() const showMetricsStub = sinon.stub(awsMetrics, 'showMetrics').resolves() await awsMetrics.hooks['metrics:metrics']() expect(extendedValidateStub.calledOnce).to.equal(true) expect(getMetricsStub.calledAfter(extendedValidateStub)).to.equal(true) expect(showMetricsStub.calledAfter(getMetricsStub)).to.equal(true) awsMetrics.extendedValidate.restore() awsMetrics.getMetrics.restore() awsMetrics.showMetrics.restore() }) }) describe('#extendedValidate()', () => { let validateStub beforeEach(() => { awsMetrics.serverless.service.functions = { function1: {}, } awsMetrics.serverless.service.service = 'my-service' awsMetrics.options.function = 'function1' validateStub = sinon.stub(awsMetrics, 'validate').resolves() }) afterEach(() => { awsMetrics.validate.restore() }) it('should call the shared validate() function', () => { awsMetrics.extendedValidate() expect(validateStub.calledOnce).to.equal(true) }) it('should set the startTime to yesterday as the default value if not provided', () => { awsMetrics.options.startTime = null let yesterday = new Date() yesterday = yesterday.setDate(yesterday.getDate() - 1) yesterday = new Date(yesterday) const yesterdaysYear = yesterday.getFullYear() const yesterdaysMonth = yesterday.getMonth() + 1 const yesterdaysDay = yesterday.getDate() const yesterdaysDate = `${yesterdaysYear}-${yesterdaysMonth}-${yesterdaysDay}` awsMetrics.extendedValidate() const defaultsStartTime = dayjs(awsMetrics.options.startTime) const defaultsDate = defaultsStartTime.format('YYYY-M-D') expect(defaultsDate).to.equal(yesterdaysDate) }) it('should set the startTime to the provided value', () => { awsMetrics.options.startTime = '1970-01-01' awsMetrics.extendedValidate() const startTime = awsMetrics.options.startTime.toISOString() const expectedStartTime = new Date('1970-01-01').toISOString() expect(startTime).to.equal(expectedStartTime) }) it('should translate human friendly syntax (e.g. 24h) for startTime', () => { awsMetrics.options.startTime = '24h' // 24 hours ago let yesterday = new Date() yesterday = yesterday.setDate(yesterday.getDate() - 1) yesterday = new Date(yesterday) const yesterdaysYear = yesterday.getFullYear() const yesterdaysMonth = yesterday.getMonth() + 1 const yesterdaysDay = yesterday.getDate() const yesterdaysDate = `${yesterdaysYear}-${yesterdaysMonth}-${yesterdaysDay}` awsMetrics.extendedValidate() const translatedStartTime = dayjs(awsMetrics.options.startTime) const translatedDate = translatedStartTime.format('YYYY-M-D') expect(translatedDate).to.equal(yesterdaysDate) }) it('should set the endTime to today as the default value if not provided', () => { awsMetrics.options.endTime = null const today = new Date() const todaysYear = today.getFullYear() const todaysMonth = today.getMonth() + 1 const todaysDay = today.getDate() const todaysDate = `${todaysYear}-${todaysMonth}-${todaysDay}` awsMetrics.extendedValidate() const defaultsStartTime = dayjs(awsMetrics.options.endTime) const defaultsDate = defaultsStartTime.format('YYYY-M-D') expect(defaultsDate).to.equal(todaysDate) }) it('should set the endTime to the provided value', () => { awsMetrics.options.endTime = '1970-01-01' awsMetrics.extendedValidate() const endTime = awsMetrics.options.endTime.toISOString() const expectedEndTime = new Date('1970-01-01').toISOString() expect(endTime).to.equal(expectedEndTime) }) }) describe('#getMetrics()', () => { let requestStub beforeEach(() => { awsMetrics.serverless.service.functions = { function1: { name: 'func1', }, function2: { name: 'func2', }, } awsMetrics.options.startTime = new Date('1970-01-01') awsMetrics.options.endTime = new Date('1970-01-02') requestStub = sinon.stub(awsMetrics.provider, 'request') }) afterEach(() => { awsMetrics.provider.request.restore() }) it('should gather service wide function metrics if no function option is specified', async () => { // stubs for function1 // invocations requestStub.onCall(0).resolves({ ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func1', }, Label: 'Invocations', Datapoints: [], }) // throttles requestStub.onCall(1).resolves({ ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func1', }, Label: 'Throttles', Datapoints: [], }) // errors requestStub.onCall(2).resolves({ ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func1', }, Label: 'Errors', Datapoints: [], }) // duration requestStub.onCall(3).resolves({ ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func1', }, Label: 'Duration', Datapoints: [], }) // stubs for function2 // invocations requestStub.onCall(4).resolves({ ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func2', }, Label: 'Invocations', Datapoints: [], }) // throttles requestStub.onCall(5).resolves({ ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func2', }, Label: 'Throttles', Datapoints: [], }) // errors requestStub.onCall(6).resolves({ ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func2', }, Label: 'Errors', Datapoints: [], }) // duration requestStub.onCall(7).resolves({ ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func2', }, Label: 'Duration', Datapoints: [], }) const expectedResult = [ [ { ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func1', }, Label: 'Invocations', Datapoints: [], }, { ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func1', }, Label: 'Throttles', Datapoints: [], }, { ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func1', }, Label: 'Errors', Datapoints: [], }, { ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func1', }, Label: 'Duration', Datapoints: [], }, ], [ { ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func2', }, Label: 'Invocations', Datapoints: [], }, { ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func2', }, Label: 'Throttles', Datapoints: [], }, { ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func2', }, Label: 'Errors', Datapoints: [], }, { ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func2', }, Label: 'Duration', Datapoints: [], }, ], ] const result = await awsMetrics.getMetrics() expect(result).to.deep.equal(expectedResult) }) it('should gather function metrics if function option is specified', async () => { // only display metrics for function1 awsMetrics.options.function = 'function1' // stubs for function1 // invocations requestStub.onCall(0).resolves({ ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func1', }, Label: 'Invocations', Datapoints: [], }) // throttles requestStub.onCall(1).resolves({ ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func1', }, Label: 'Throttles', Datapoints: [], }) // errors requestStub.onCall(2).resolves({ ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func1', }, Label: 'Errors', Datapoints: [], }) // duration requestStub.onCall(3).resolves({ ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func1', }, Label: 'Duration', Datapoints: [], }) const expectedResult = [ [ { ResponseMetadata: { RequestId: '1f50045b-b569-11e6-86c6-eb54d1aaa755-func1', }, Label: 'Invocations', Datapoints: [], }, { ResponseMetadata: { RequestId: '1f59059b-b569-11e6-aa18-c7bab68810d2-func1', }, Label: 'Throttles', Datapoints: [], }, { ResponseMetadata: { RequestId: '1f50c7b1-b569-11e6-b1b6-ab86694b617b-func1', }, Label: 'Errors', Datapoints: [], }, { ResponseMetadata: { RequestId: '1f63db14-b569-11e6-8501-d98a275ce164-func1', }, Label: 'Duration', Datapoints: [], }, ], ] const result = await awsMetrics.getMetrics() expect(result).to.deep.equal(expectedResult) }) it('should gather metrics with 1 hour period for time span < 24 hours', async () => { awsMetrics.options.startTime = new Date('1970-01-01T09:00') awsMetrics.options.endTime = new Date('1970-01-01T16:00') await awsMetrics.getMetrics() expect( requestStub.calledWith( sinon.match.string, sinon.match.string, sinon.match.has('Period', 3600), ), ).to.equal(true) }) it('should gather metrics with 1 day period for time span > 24 hours', async () => { awsMetrics.options.startTime = new Date('1970-01-01') awsMetrics.options.endTime = new Date('1970-01-03') await awsMetrics.getMetrics() expect( requestStub.calledWith( sinon.match.string, sinon.match.string, sinon.match.has('Period', 24 * 3600), ), ).to.equal(true) }) }) })