text: 'use strict';
  
  /* eslint-disable no-unused-expressions */
  
  const _ = require('lodash');
  const chai = require('chai');
  const path = require('path');
  const fs = require('fs-extra');
  const os = require('os');
  const proxyquire = require('proxyquire');
  const sinon = require('sinon');
  const overrideEnv = require('process-utils/override-env');
  
  const AwsProvider = require('../../../../../lib/plugins/aws/provider');
  const Serverless = require('../../../../../lib/Serverless');
  const runServerless = require('../../../../utils/run-serverless');
  
  chai.use(require('chai-as-promised'));
  chai.use(require('sinon-chai'));
  
  const expect = chai.expect;
  
  describe('AwsProvider', () => {
    let awsProvider;
    let serverless;
    let restoreEnv;
    const options = {
      stage: 'dev',
      region: 'us-east-1',
    };
  
    beforeEach(() => {
      ({ restoreEnv } = overrideEnv());
      serverless = new Serverless(options);
      serverless.cli = new serverless.classes.CLI();
      awsProvider = new AwsProvider(serverless, options);
    });
    afterEach(() => restoreEnv());
  
    describe('#constructor()', () => {
      it('should set Serverless instance', () => {
        expect(typeof awsProvider.serverless).to.not.equal('undefined');
      });
  
      it('should set the provider property', () => {
        expect(awsProvider.provider).to.equal(awsProvider);
      });
  
      describe('stage name validation', () => {
        const stages = ['myStage', 'my-stage', 'my_stage', "${opt:stage, 'prod'}"];
        stages.forEach((stage) => {
          it(`should not throw an error before variable population
              even if http event is present and stage is ${stage}`, () => {
            const config = {
              stage,
            };
            serverless = new Serverless(config);
  
            const serverlessYml = {
              service: 'new-service',
              provider: {
                name: 'aws',
                stage,
              },
              functions: {
                first: {
                  events: [
                    {
                      http: {
                        path: 'foo',
                        method: 'GET',
                      },
                    },
                  ],
                },
              },
            };
            serverless.service = new serverless.classes.Service(serverless, serverlessYml);
            expect(() => new AwsProvider(serverless, config)).to.not.throw(Error);
          });
        });
      });
  
      describe('deploymentBucket configuration', () => {
        it('should do nothing if not defined', () => {
          serverless.service.provider.deploymentBucket = undefined;
  
          const newAwsProvider = new AwsProvider(serverless, options);
  
          expect(newAwsProvider.serverless.service.provider.deploymentBucket).to.equal(undefined);
        });
  
        it('should do nothing if the value is a string', () => {
          serverless.service.provider.deploymentBucket = 'my.deployment.bucket';
  
          const newAwsProvider = new AwsProvider(serverless, options);
  
          expect(newAwsProvider.serverless.service.provider.deploymentBucket).to.equal(
            'my.deployment.bucket'
          );
        });
      });
    });
  
    describe('values', () => {
      const obj = {
        a: 'b',
        c: {
          d: 'e',
          f: {
            g: 'h',
          },
        },
      };
      const paths = [['a'], ['c', 'd'], ['c', 'f', 'g']];
      const getExpected = [
        { path: paths[0], value: obj.a },
        { path: paths[1], value: obj.c.d },
        { path: paths[2], value: obj.c.f.g },
      ];
      describe('#getValues', () => {
        it('should return an array of values given paths to them', () => {
          expect(awsProvider.getValues(obj, paths)).to.eql(getExpected);
        });
      });
      describe('#firstValue', () => {
        it("should ignore entries without a 'value' attribute", () => {
          const input = _.cloneDeep(getExpected);
          delete input[0].value;
          delete input[2].value;
          expect(awsProvider.firstValue(input)).to.eql(getExpected[1]);
        });
        it("should ignore entries with an undefined 'value' attribute", () => {
          const input = _.cloneDeep(getExpected);
          input[0].value = undefined;
          input[2].value = undefined;
          expect(awsProvider.firstValue(input)).to.eql(getExpected[1]);
        });
        it('should return the first value', () => {
          expect(awsProvider.firstValue(getExpected)).to.equal(getExpected[0]);
        });
        it('should return the middle value', () => {
          const input = _.cloneDeep(getExpected);
          delete input[0].value;
          delete input[2].value;
          expect(awsProvider.firstValue(input)).to.equal(input[1]);
        });
        it('should return the last value', () => {
          const input = _.cloneDeep(getExpected);
          delete input[0].value;
          delete input[1].value;
          expect(awsProvider.firstValue(input)).to.equal(input[2]);
        });
        it('should return the last object if none have valid values', () => {
          const input = _.cloneDeep(getExpected);
          delete input[0].value;
          delete input[1].value;
          delete input[2].value;
          expect(awsProvider.firstValue(input)).to.equal(input[2]);
        });
      });
    });
  
    describe('#request()', () => {
      let awsRequestStub;
      let awsProviderProxied;
      let logStub;
  
      beforeEach(() => {
        logStub = sinon.stub();
        awsRequestStub = sinon.stub().resolves();
        awsRequestStub.memoized = sinon.stub().resolves();
        const AwsProviderProxyquired = proxyquire
          .noCallThru()
          .load('../../../../../lib/plugins/aws/provider.js', {
            '../../aws/request': awsRequestStub,
            '@serverless/utils/log': logStub,
          });
        awsProviderProxied = new AwsProviderProxyquired(serverless, options);
      });
  
      afterEach(() => {});
  
      it('should pass resolved credentials as expected', async () => {
        awsProviderProxied.cachedCredentials = {
          accessKeyId: 'accessKeyId',
          secretAccessKey: 'secretAccessKey',
          sessionToken: 'sessionToken',
        };
        await awsProviderProxied.request('S3', 'getObject', {});
        expect(awsRequestStub.args[0][0]).to.deep.equal({
          name: 'S3',
          params: {
            ...awsProviderProxied.cachedCredentials,
            region: 'us-east-1',
            isS3TransferAccelerationEnabled: false,
          },
        });
      });
  
      it('should trigger the expected AWS SDK invokation', () => {
        return awsProviderProxied.request('S3', 'getObject', {}).then(() => {
          expect(awsRequestStub).to.have.been.calledOnce;
        });
      });
  
      it('should use local cache when using {useCache: true}', () => {
        return awsProviderProxied
          .request('S3', 'getObject', {}, { useCache: true })
          .then(() => awsProviderProxied.request('S3', 'getObject', {}, { useCache: true }))
          .then(() => {
            expect(awsRequestStub).to.not.have.been.called;
            expect(awsRequestStub.memoized).to.have.been.calledTwice;
          });
      });
  
      it('should detect incompatible legacy use of aws request and print a debug warning', () => {
        // Enable debug log
        process.env.SLS_DEBUG = true;
        return awsProviderProxied
          .request('S3', 'getObject', {}, 'incompatible string option')
          .then(() => {
            expect(logStub).to.have.been.calledWith(
              'WARNING: Inappropriate call of provider.request()'
            );
          })
          .finally(() => {
            process.env.SLS_DEBUG = false;
          });
      });
    });
  
    describe('#getServerlessDeploymentBucketName()', () => {
      it('should return the name of the serverless deployment bucket', () => {
        const describeStackResourcesStub = sinon.stub(awsProvider, 'request').resolves({
          StackResourceDetail: {
            PhysicalResourceId: 'serverlessDeploymentBucketName',
          },
        });
  
        return awsProvider.getServerlessDeploymentBucketName().then((bucketName) => {
          expect(bucketName).to.equal('serverlessDeploymentBucketName');
          expect(describeStackResourcesStub.calledOnce).to.be.equal(true);
          expect(
            describeStackResourcesStub.calledWithExactly('CloudFormation', 'describeStackResource', {
              StackName: awsProvider.naming.getStackName(),
              LogicalResourceId: awsProvider.naming.getDeploymentBucketLogicalId(),
            })
          ).to.be.equal(true);
          awsProvider.request.restore();
        });
      });
  
      it('should return the name of the custom deployment bucket', () => {
        awsProvider.serverless.service.provider.deploymentBucket = 'custom-bucket';
  
        const describeStackResourcesStub = sinon.stub(awsProvider, 'request').resolves({
          StackResourceDetail: {
            PhysicalResourceId: 'serverlessDeploymentBucketName',
          },
        });
  
        return awsProvider.getServerlessDeploymentBucketName().then((bucketName) => {
          expect(describeStackResourcesStub.called).to.be.equal(false);
          expect(bucketName).to.equal('custom-bucket');
          awsProvider.request.restore();
        });
      });
    });
  
    describe('#getAccountInfo()', () => {
      it('should return the AWS account id and partition', () => {
        const accountId = '12345678';
        const partition = 'aws';
  
        const stsGetCallerIdentityStub = sinon.stub(awsProvider, 'request').resolves({
          ResponseMetadata: { RequestId: '12345678-1234-1234-1234-123456789012' },
          UserId: 'ABCDEFGHIJKLMNOPQRSTU:VWXYZ',
          Account: accountId,
          Arn: 'arn:aws:sts::123456789012:assumed-role/ROLE-NAME/VWXYZ',
        });
  
        return awsProvider.getAccountInfo().then((result) => {
          expect(stsGetCallerIdentityStub.calledOnce).to.equal(true);
          expect(result.accountId).to.equal(accountId);
          expect(result.partition).to.equal(partition);
          awsProvider.request.restore();
        });
      });
    });
  
    describe('#getAccountId()', () => {
      it('should return the AWS account id', () => {
        const accountId = '12345678';
  
        const stsGetCallerIdentityStub = sinon.stub(awsProvider, 'request').resolves({
          ResponseMetadata: { RequestId: '12345678-1234-1234-1234-123456789012' },
          UserId: 'ABCDEFGHIJKLMNOPQRSTU:VWXYZ',
          Account: accountId,
          Arn: 'arn:aws:sts::123456789012:assumed-role/ROLE-NAME/VWXYZ',
        });
  
        return awsProvider.getAccountId().then((result) => {
          expect(stsGetCallerIdentityStub.calledOnce).to.equal(true);
          expect(result).to.equal(accountId);
          awsProvider.request.restore();
        });
      });
    });
  
    describe('#isS3TransferAccelerationEnabled()', () => {
      it('should return false by default', () => {
        awsProvider.options['aws-s3-accelerate'] = undefined;
        return expect(awsProvider.isS3TransferAccelerationEnabled()).to.equal(false);
      });
      it('should return true when CLI option is provided', () => {
        awsProvider.options['aws-s3-accelerate'] = true;
        return expect(awsProvider.isS3TransferAccelerationEnabled()).to.equal(true);
      });
    });
  
    describe('#disableTransferAccelerationForCurrentDeploy()', () => {
      it('should remove the corresponding option for the current deploy', () => {
        awsProvider.options['aws-s3-accelerate'] = true;
        awsProvider.disableTransferAccelerationForCurrentDeploy();
        return expect(awsProvider.options['aws-s3-accelerate']).to.be.undefined;
      });
    });
  });
  
  describe('test/unit/lib/plugins/aws/provider.test.js', () => {
    describe('#getProviderName and #sessionCache', () => {
      let sls;
      const expectedToken = '123';
  
      before(async () => {
        // Fake service that update credentials
        class FakeCloudFormation {
          constructor(credentials) {
            this.credentials = credentials;
            this.credentials.credentials.sessionToken = expectedToken;
          }
          describeStacks() {
            return { promise: async () => {} };
          }
        }
        // Stub functions for the credentials creation in the provider
        class SharedIniFileCredentials {
          constructor() {
            this.sessionToken = 'abc';
            this.accessKeyId = 'keyId';
            this.secretAccessKey = 'secret';
          }
        }
        class EnvironmentCredentials {
          constructor() {
            this.sessionToken = 'env';
            this.accessKeyId = 'keyId';
            this.secretAccessKey = 'secret';
          }
        }
        const modulesCacheStub = {
          'aws-sdk': {
            SharedIniFileCredentials,
            EnvironmentCredentials,
            CloudFormation: FakeCloudFormation,
          },
        };
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          modulesCacheStub,
        });
        sls = serverless;
      });
  
      it('`AwsProvider.getProviderName()` should resolve provider name', () => {
        expect(AwsProvider.getProviderName()).to.equal('aws');
      });
  
      it('should retain sessionToken eventually updated internally by SDK', async () => {
        expect(sls.getProvider('aws').getCredentials().credentials.sessionToken).not.to.equal(
          expectedToken
        );
        await sls.getProvider('aws').request('CloudFormation', 'describeStacks');
        expect(sls.getProvider('aws').getCredentials().credentials.sessionToken).to.equal(
          expectedToken
        );
      });
    });
  
    describe('#getCredentials()', () => {
      before(async () => {
        // create default aws credentials file in before so that grouped run can use it
        await fs.ensureDir(path.resolve(os.homedir(), './.aws'));
        await fs.outputFile(
          path.resolve(os.homedir(), './.aws/credentials'),
          `
  [default]
  aws_access_key_id = DEFAULTKEYID
  aws_secret_access_key = DEFAULTSECRET
  
  [notDefault]
  aws_access_key_id = NOTDEFAULTKEYID
  aws_secret_access_key = NOTDEFAULTSECRET
  
  [notDefaultWithRole]
  source_profile = notDefault
  role_arn = NOTDEFAULTWITHROLEROLE
  `,
          { flag: 'w+' }
        );
      });
  
      it('should get credentials from default AWS profile', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
        });
        const awsCredentials = serverless.getProvider('aws').getCredentials();
        expect(awsCredentials.credentials.accessKeyId).to.equal('DEFAULTKEYID');
      });
  
      it('should get credentials from custom default AWS profile, set by AWS_DEFAULT_PROFILE', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
        });
        // getCredentials resolve the env when called
        let awsCredentials;
        overrideEnv(() => {
          process.env.AWS_DEFAULT_PROFILE = 'notDefault';
          awsCredentials = serverless.getProvider('aws').getCredentials();
        });
        expect(awsCredentials.credentials.accessKeyId).to.equal('NOTDEFAULTKEYID');
      });
  
      describe('assume role with provider.profile', () => {
        let awsCredentials;
        before(async () => {
          const { serverless } = await runServerless({
            fixture: 'aws',
            command: 'print',
            configExt: {
              provider: {
                profile: 'notDefaultWithRole',
              },
            },
          });
          awsCredentials = serverless.getProvider('aws').getCredentials();
        });
  
        it('should get credentials from `provider.profile`', () => {
          expect(awsCredentials.credentials.profile).to.equal('notDefaultWithRole');
        });
  
        it('should accept a role to assume on credentials', () => {
          expect(awsCredentials.credentials.roleArn).to.equal('NOTDEFAULTWITHROLEROLE');
        });
      });
  
      it('should get credentials from environment variables', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
        });
        let awsCredentials;
        // getCredentials resolve the env when called
        overrideEnv(() => {
          process.env.AWS_ACCESS_KEY_ID = 'ENVKEYID';
          process.env.AWS_SECRET_ACCESS_KEY = 'ENVSECRET';
          awsCredentials = serverless.getProvider('aws').getCredentials();
        });
        expect(awsCredentials.credentials.accessKeyId).to.equal('ENVKEYID');
      });
  
      describe('profile with non default credentials file', () => {
        let awsCredentials;
        before(async () => {
          await fs.outputFile(
            path.resolve(os.homedir(), './custom_credentials'),
            `
  [default]
  aws_access_key_id = DEFAULTKEYID
  aws_secret_access_key = DEFAULTSECRET
  
  [customProfile]
  aws_access_key_id = CUSTOMKEYID
  aws_secret_access_key = CUSTOMSECRET
  `,
            { flag: 'w+' }
          );
          const { serverless } = await runServerless({
            fixture: 'aws',
            command: 'print',
          });
          // getCredentials resolve the env when called
          overrideEnv(() => {
            process.env.AWS_PROFILE = 'customProfile';
            process.env.AWS_SHARED_CREDENTIALS_FILE = path
              .resolve(os.homedir(), './custom_credentials')
              .toString();
            awsCredentials = serverless.getProvider('aws').getCredentials();
          });
        });
  
        after(async () => {
          await fs.remove(path.resolve(os.homedir(), './custom_credentials'));
        });
  
        it('should get credentials from AWS_PROFILE environment variable', () => {
          expect(awsCredentials.credentials.profile).to.equal('customProfile');
        });
  
        it('should get credentials from AWS_SHARED_CREDENTIALS_FILE environment variable', () => {
          expect(awsCredentials.credentials.accessKeyId).to.equal('CUSTOMKEYID');
        });
      });
  
      it('should get credentials from stage specific environment variables', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          configExt: {
            provider: {
              stage: 'testStage',
            },
          },
        });
        let awsCredentials;
        overrideEnv(() => {
          process.env.AWS_TESTSTAGE_ACCESS_KEY_ID = 'TESTSTAGEACCESSKEYID';
          process.env.AWS_TESTSTAGE_SECRET_ACCESS_KEY = 'TESTSTAGESECRET';
          awsCredentials = serverless.getProvider('aws').getCredentials();
        });
        expect(awsCredentials.credentials.accessKeyId).to.equal('TESTSTAGEACCESSKEYID');
      });
  
      it('should get credentials from AWS_{stage}_PROFILE environment variable', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          configExt: {
            provider: {
              stage: 'testStage',
            },
          },
        });
        let awsCredentials;
        overrideEnv(() => {
          process.env.AWS_TESTSTAGE_PROFILE = 'notDefault';
          awsCredentials = serverless.getProvider('aws').getCredentials();
        });
        expect(awsCredentials.credentials.accessKeyId).to.equal('NOTDEFAULTKEYID');
      });
  
      describe('profile with cli and encryption', () => {
        let awsCredentials;
        before(async () => {
          const { serverless } = await runServerless({
            fixture: 'aws',
            command: 'print',
            options: {
              'aws-profile': 'notDefault',
            },
            configExt: {
              provider: {
                deploymentBucket: {
                  serverSideEncryption: 'aws:kms',
                },
              },
            },
          });
          awsCredentials = serverless.getProvider('aws').getCredentials();
        });
  
        it('should get credentials "--aws-profile" CLI option', () => {
          expect(awsCredentials.credentials.accessKeyId).to.equal('NOTDEFAULTKEYID');
        });
  
        it('should set the signatureVersion to v4 if the serverSideEncryption is aws:kms', () => {
          expect(awsCredentials.signatureVersion).to.equal('v4');
        });
      });
  
      it('should throw an error if a non-existent profile is set', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          options: {
            'aws-profile': 'nonExistent',
          },
        });
        expect(() => serverless.getProvider('aws').getCredentials()).to.throw(Error);
      });
    });
  
    describe('#getRegion()', () => {
      it('should default to "us-east-1"', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
        });
        expect(serverless.getProvider('aws').getRegion()).to.equal('us-east-1');
      });
  
      it('should allow to override via `provider.region`', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          configExt: {
            provider: {
              region: 'eu-central-1',
            },
          },
        });
        expect(serverless.getProvider('aws').getRegion()).to.equal('eu-central-1');
      });
  
      it('should allow to override via CLI `--region` param', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          options: { region: 'us-west-1' },
          configExt: {
            provider: {
              region: 'eu-central-1',
            },
          },
        });
        expect(serverless.getProvider('aws').getRegion()).to.equal('us-west-1');
      });
    });
  
    describe('#getProfile()', () => {
      it('should allow to set via `provider.profile`', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          configExt: {
            provider: {
              profile: 'test-profile',
            },
          },
        });
        expect(serverless.getProvider('aws').getProfile()).to.equal('test-profile');
      });
  
      it('should allow to set via CLI `--profile` param', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          options: { profile: 'cli-override' },
          configExt: {
            provider: {
              profile: 'test-profile',
            },
          },
        });
        expect(serverless.getProvider('aws').getProfile()).to.equal('cli-override');
      });
  
      it('should allow to set via CLI `--aws-profile` param', async () => {
        // Test with `provider.profile` `--profile` and `--aws-pofile` CLI param set
        // Confirm that `--aws-profile` overrides
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          options: {
            'profile': 'cli-override',
            'aws-profile': 'aws-override',
          },
          configExt: {
            provider: {
              profile: 'test-profile',
            },
          },
        });
        expect(serverless.getProvider('aws').getProfile()).to.equal('aws-override');
      });
    });
  
    describe('#getDeploymentPrefix()', () => {
      it('should put all artifacts in namespaced folder', async () => {
        const { cfTemplate } = await runServerless({
          fixture: 'function',
          command: 'package',
        });
        const functions = Object.values(cfTemplate.Resources).filter(
          (r) => r.Type === 'AWS::Lambda::Function'
        );
        expect(functions.length).to.be.greaterThanOrEqual(1);
        for (const f of functions) {
          expect(f.Properties.Code.S3Key)
            .to.be.a('string')
            .and.satisfy((key) => key.startsWith('serverless/'));
        }
      });
  
      it('should support custom namespaced folder', async () => {
        const { cfTemplate } = await runServerless({
          fixture: 'function',
          command: 'package',
          configExt: {
            provider: {
              deploymentPrefix: 'test-prefix',
            },
          },
        });
        const functions = Object.values(cfTemplate.Resources).filter(
          (r) => r.Type === 'AWS::Lambda::Function'
        );
        expect(functions.length).to.be.greaterThanOrEqual(1);
        for (const f of functions) {
          expect(f.Properties.Code.S3Key)
            .to.be.a('string')
            .and.satisfy((key) => key.startsWith('test-prefix/'));
        }
      });
    });
  
    describe('#getAlbTargetGroupPrefix()', () => {
      it('should support `provider.alb.targetGroupPrefix`', async () => {
        const albId = '50dc6c495c0c9188';
        const validBaseEventConfig = {
          listenerArn: `arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-load-balancer/${albId}/f2f7dc8efc522ab2`,
          conditions: {
            path: '/',
          },
        };
        const { cfTemplate } = await runServerless({
          fixture: 'function',
          command: 'package',
          configExt: {
            provider: {
              alb: {
                targetGroupPrefix: 'a-prefix',
              },
            },
            functions: {
              fnTargetGroupName: {
                handler: 'index.handler',
                events: [
                  {
                    alb: {
                      ...validBaseEventConfig,
                      priority: 1,
                    },
                  },
                ],
              },
            },
          },
        });
        const targetGroups = Object.values(cfTemplate.Resources).filter(
          (r) => r.Type === 'AWS::ElasticLoadBalancingV2::TargetGroup'
        );
        expect(targetGroups.length).to.be.greaterThanOrEqual(1);
        for (const t of targetGroups) {
          expect(t.Properties.Name)
            .to.be.a('string')
            .and.satisfy((key) => key.startsWith('a-prefix'));
        }
      });
    });
  
    describe('#getStage()', () => {
      it('should default to "dev"', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
        });
        expect(serverless.getProvider('aws').getStage()).to.equal('dev');
      });
  
      it('should allow to override via `provider.stage`', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          configExt: {
            provider: {
              stage: 'staging',
            },
          },
        });
        expect(serverless.getProvider('aws').getStage()).to.equal('staging');
      });
  
      it('should allow to override via CLI `--stage` param', async () => {
        const { serverless } = await runServerless({
          fixture: 'aws',
          command: 'print',
          options: { stage: 'production' },
          configExt: {
            provider: {
              stage: 'staging',
            },
          },
        });
        expect(serverless.getProvider('aws').getStage()).to.equal('production');
      });
    });
  
    describe('when resolving images', () => {
      it('should fail if `functions[].image` references image with both path and uri', async () => {
        await expect(
          runServerless({
            fixture: 'function',
            command: 'package',
            configExt: {
              provider: {
                ecr: {
                  images: {
                    invalidimage: {
                      path: './',
                      uri:
                        '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38',
                    },
                  },
                },
              },
              functions: {
                fnProviderInvalidImage: {
                  image: 'invalidimage',
                },
              },
            },
          })
        ).to.be.eventually.rejected.and.have.property(
          'code',
          'ECR_IMAGE_BOTH_URI_AND_PATH_DEFINED_ERROR'
        );
      });
  
      it('should fail if `functions[].image` references image without path and uri', async () => {
        await expect(
          runServerless({
            fixture: 'function',
            command: 'package',
            configExt: {
              provider: {
                ecr: {
                  images: {
                    invalidimage: {},
                  },
                },
              },
              functions: {
                fnProviderInvalidImage: {
                  image: 'invalidimage',
                },
              },
            },
          })
        ).to.be.eventually.rejected.and.have.property(
          'code',
          'ECR_IMAGE_NEITHER_URI_NOR_PATH_DEFINED_ERROR'
        );
      });
  
      it('should fail if `functions[].image` references image from `provider.ecr.images` that has invalid path', async () => {
        await expect(
          runServerless({
            fixture: 'ecr',
            command: 'package',
            shouldStubSpawn: true,
            configExt: {
              provider: {
                ecr: {
                  images: {
                    baseimage: {
                      path: './invalid',
                    },
                  },
                },
              },
            },
          })
        ).to.be.eventually.rejected.and.have.property('code', 'DOCKERFILE_NOT_AVAILABLE_ERROR');
      });
  
      it('should fail if `functions[].image` references image not defined in `provider.ecr.images`', async () => {
        await expect(
          runServerless({
            fixture: 'function',
            command: 'package',
            configExt: {
              functions: {
                fnInvalid: {
                  image: 'undefinedimage',
                },
              },
            },
          })
        ).to.be.eventually.rejected.and.have.property(
          'code',
          'REFERENCED_FUNCTION_IMAGE_NOT_DEFINED_IN_PROVIDER'
        );
      });
  
      it('should fail if both uri and name is provided for an image', async () => {
        await expect(
          runServerless({
            fixture: 'ecr',
            command: 'package',
            shouldStubSpawn: true,
            configExt: {
              functions: {
                foo: {
                  image: {
                    name: 'baseimage',
                    uri:
                      '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38',
                  },
                },
              },
            },
          })
        ).to.be.eventually.rejected.and.have.property(
          'code',
          'FUNCTION_IMAGE_BOTH_URI_AND_NAME_DEFINED_ERROR'
        );
      });
  
      it('should fail if neither uri nor name is provided for an image', async () => {
        await expect(
          runServerless({
            fixture: 'ecr',
            command: 'package',
            shouldStubSpawn: true,
            configExt: {
              functions: {
                foo: {
                  image: {},
                },
              },
            },
          })
        ).to.be.eventually.rejected.and.have.property(
          'code',
          'FUNCTION_IMAGE_NEITHER_URI_NOR_NAME_DEFINED_ERROR'
        );
      });
  
      const findVersionCfConfig = (cfResources, fnLogicalId) =>
        Object.values(cfResources).find(
          (resource) =>
            resource.Type === 'AWS::Lambda::Version' &&
            resource.Properties.FunctionName.Ref === fnLogicalId
        ).Properties;
  
      describe('with `functions[].image` referencing existing images', () => {
        let cfResources;
        let naming;
        let serviceConfig;
        const imageSha = '6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38';
        const imageWithSha = `000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker@sha256:${imageSha}`;
        const imageDigestFromECR =
          'sha256:2e6b10a4b1ca0f6d3563a8a1f034dde7c4d7c93b50aa91f24311765d0822186b';
        const describeImagesStub = sinon
          .stub()
          .resolves({ imageDetails: [{ imageDigest: imageDigestFromECR }] });
        const awsRequestStubMap = {
          ECR: {
            describeImages: describeImagesStub,
          },
        };
  
        before(async () => {
          const { awsNaming, cfTemplate, fixtureData } = await runServerless({
            fixture: 'function',
            command: 'package',
            configExt: {
              provider: {
                ecr: {
                  images: {
                    imagewithexplicituri: {
                      uri: imageWithSha,
                    },
                    imagewithimplicituri: imageWithSha,
                  },
                },
              },
              functions: {
                fnImage: {
                  image: imageWithSha,
                },
                fnImageWithTag: {
                  image: '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker:stable',
                },
                fnImageWithTagAndRepoWithSlashes: {
                  image:
                    '000000000000.dkr.ecr.sa-east-1.amazonaws.com/test-lambda/repo-docker:stable',
                },
                fnImageWithExplicitUri: {
                  image: {
                    uri: imageWithSha,
                  },
                },
                fnProviderImageWithExplicitUri: {
                  image: 'imagewithexplicituri',
                },
                fnProviderImageWithImplicitUri: {
                  image: 'imagewithimplicituri',
                },
              },
            },
            awsRequestStubMap,
          });
          cfResources = cfTemplate.Resources;
          naming = awsNaming;
          serviceConfig = fixtureData.serviceConfig;
        });
  
        it('should support `functions[].image` with implicit uri with sha', () => {
          const functionServiceConfig = serviceConfig.functions.fnImage;
          const functionCfLogicalId = naming.getLambdaLogicalId('fnImage');
          const functionCfConfig = cfResources[functionCfLogicalId].Properties;
  
          expect(functionCfConfig.Code).to.deep.equal({ ImageUri: functionServiceConfig.image });
          expect(functionCfConfig).to.not.have.property('Handler');
          expect(functionCfConfig).to.not.have.property('Runtime');
  
          const imageDigest = functionServiceConfig.image.slice(
            functionServiceConfig.image.lastIndexOf('@') + 1
          );
          expect(imageDigest).to.match(/^sha256:[a-f0-9]{64}$/);
          const imageDigestSha = imageDigest.slice('sha256:'.length);
          const versionCfConfig = findVersionCfConfig(cfResources, functionCfLogicalId);
          expect(versionCfConfig.CodeSha256).to.equal(imageDigestSha);
        });
  
        it('should support `functions[].image` with explicit uri with sha', () => {
          const functionServiceConfig = serviceConfig.functions.fnImageWithExplicitUri;
          const functionCfLogicalId = naming.getLambdaLogicalId('fnImageWithExplicitUri');
          const functionCfConfig = cfResources[functionCfLogicalId].Properties;
  
          expect(functionCfConfig.Code).to.deep.equal({ ImageUri: functionServiceConfig.image.uri });
          expect(functionCfConfig).to.not.have.property('Handler');
          expect(functionCfConfig).to.not.have.property('Runtime');
  
          const imageDigest = functionServiceConfig.image.uri.slice(
            functionServiceConfig.image.uri.lastIndexOf('@') + 1
          );
          expect(imageDigest).to.match(/^sha256:[a-f0-9]{64}$/);
          const imageDigestSha = imageDigest.slice('sha256:'.length);
          const versionCfConfig = findVersionCfConfig(cfResources, functionCfLogicalId);
          expect(versionCfConfig.CodeSha256).to.equal(imageDigestSha);
        });
  
        it('should support `functions[].image` with tag', () => {
          const functionServiceConfig = serviceConfig.functions.fnImageWithTag;
          const functionCfLogicalId = naming.getLambdaLogicalId('fnImageWithTag');
          const functionCfConfig = cfResources[functionCfLogicalId].Properties;
  
          expect(functionCfConfig.Code).to.deep.equal({
            ImageUri: `${functionServiceConfig.image.split(':')[0]}@${imageDigestFromECR}`,
          });
          expect(functionCfConfig).to.not.have.property('Handler');
          expect(functionCfConfig).to.not.have.property('Runtime');
  
          const versionCfConfig = findVersionCfConfig(cfResources, functionCfLogicalId);
          expect(versionCfConfig.CodeSha256).to.equal(imageDigestFromECR.slice('sha256:'.length));
          expect(describeImagesStub).to.be.calledWith({
            imageIds: [{ imageTag: 'stable' }],
            registryId: '000000000000',
            repositoryName: 'test-lambda-docker',
          });
        });
  
        it('should support `functions[].image` with tag and repository name with slash', () => {
          const functionServiceConfig = serviceConfig.functions.fnImageWithTagAndRepoWithSlashes;
          const functionCfLogicalId = naming.getLambdaLogicalId('fnImageWithTagAndRepoWithSlashes');
          const functionCfConfig = cfResources[functionCfLogicalId].Properties;
  
          expect(functionCfConfig.Code).to.deep.equal({
            ImageUri: `${functionServiceConfig.image.split(':')[0]}@${imageDigestFromECR}`,
          });
          expect(functionCfConfig).to.not.have.property('Handler');
          expect(functionCfConfig).to.not.have.property('Runtime');
  
          const versionCfConfig = findVersionCfConfig(cfResources, functionCfLogicalId);
          expect(versionCfConfig.CodeSha256).to.equal(imageDigestFromECR.slice('sha256:'.length));
          expect(describeImagesStub).to.be.calledWith({
            imageIds: [{ imageTag: 'stable' }],
            registryId: '000000000000',
            repositoryName: 'test-lambda/repo-docker',
          });
        });
  
        it('should support `functions[].image` that references provider.ecr.images defined with explicit uri', () => {
          const functionCfLogicalId = naming.getLambdaLogicalId('fnProviderImageWithExplicitUri');
          const functionCfConfig = cfResources[functionCfLogicalId].Properties;
  
          expect(functionCfConfig.Code).to.deep.equal({
            ImageUri: imageWithSha,
          });
          expect(functionCfConfig).to.not.have.property('Handler');
          expect(functionCfConfig).to.not.have.property('Runtime');
  
          const versionCfConfig = findVersionCfConfig(cfResources, functionCfLogicalId);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
        });
  
        it('should support `functions[].image` that references provider.ecr.images defined with implicit uri', () => {
          const functionCfLogicalId = naming.getLambdaLogicalId('fnProviderImageWithImplicitUri');
          const functionCfConfig = cfResources[functionCfLogicalId].Properties;
  
          expect(functionCfConfig.Code).to.deep.equal({
            ImageUri: imageWithSha,
          });
          expect(functionCfConfig).to.not.have.property('Handler');
          expect(functionCfConfig).to.not.have.property('Runtime');
  
          const versionCfConfig = findVersionCfConfig(cfResources, functionCfLogicalId);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
        });
      });
  
      describe('with `functions[].image` referencing images that require building', () => {
        const imageSha = '6bb600b4d6e1d7cf521097177dd0c4e9ea373edb91984a505333be8ac9455d38';
        const repositoryUri = '999999999999.dkr.ecr.sa-east-1.amazonaws.com/test-lambda-docker';
        const authorizationToken = 'YXdzOmRvY2tlcmF1dGh0b2tlbg==';
        const proxyEndpoint = `https://${repositoryUri}`;
        const describeRepositoriesStub = sinon.stub();
        const createRepositoryStub = sinon.stub();
        const baseAwsRequestStubMap = {
          STS: {
            getCallerIdentity: {
              ResponseMetadata: { RequestId: 'ffffffff-ffff-ffff-ffff-ffffffffffff' },
              UserId: 'XXXXXXXXXXXXXXXXXXXXX',
              Account: '999999999999',
              Arn: 'arn:aws:iam::999999999999:user/test',
            },
          },
          ECR: {
            describeRepositories: {
              repositories: [{ repositoryUri }],
            },
            getAuthorizationToken: {
              authorizationData: [
                {
                  proxyEndpoint,
                  authorizationToken,
                },
              ],
            },
          },
        };
        const spawnExtStub = sinon.stub().returns({
          stdBuffer: `digest: sha256:${imageSha} size: 1787`,
        });
        const modulesCacheStub = {
          'child-process-ext/spawn': spawnExtStub,
          './lib/utils/telemetry/generatePayload.js': async () => ({}),
        };
  
        beforeEach(() => {
          describeRepositoriesStub.reset();
          createRepositoryStub.reset();
          spawnExtStub.resetHistory();
        });
  
        it('should work correctly when repository exists beforehand', async () => {
          const awsRequestStubMap = {
            ...baseAwsRequestStubMap,
            ECR: {
              ...baseAwsRequestStubMap.ECR,
              describeRepositories: describeRepositoriesStub.resolves({
                repositories: [{ repositoryUri }],
              }),
              createRepository: createRepositoryStub,
            },
          };
          const {
            awsNaming,
            cfTemplate,
            fixtureData: { servicePath: serviceDir },
          } = await runServerless({
            fixture: 'ecr',
            command: 'package',
            awsRequestStubMap,
            modulesCacheStub,
          });
  
          const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo');
          const functionCfConfig = cfTemplate.Resources[functionCfLogicalId].Properties;
          const versionCfConfig = findVersionCfConfig(cfTemplate.Resources, functionCfLogicalId);
  
          expect(functionCfConfig.Code.ImageUri).to.deep.equal(`${repositoryUri}@sha256:${imageSha}`);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
          expect(describeRepositoriesStub).to.be.calledOnce;
          expect(createRepositoryStub.notCalled).to.be.true;
          expect(spawnExtStub).to.be.calledWith('docker', ['--version']);
          expect(spawnExtStub).not.to.be.calledWith('docker', [
            'login',
            '--username',
            'AWS',
            '--password',
            'dockerauthtoken',
            proxyEndpoint,
          ]);
          expect(spawnExtStub).to.be.calledWith('docker', [
            'build',
            '-t',
            `${awsNaming.getEcrRepositoryName()}:baseimage`,
            '-f',
            path.join(serviceDir, 'Dockerfile'),
            './',
          ]);
          expect(spawnExtStub).to.be.calledWith('docker', [
            'tag',
            `${awsNaming.getEcrRepositoryName()}:baseimage`,
            `${repositoryUri}:baseimage`,
          ]);
          expect(spawnExtStub).to.be.calledWith('docker', ['push', `${repositoryUri}:baseimage`]);
        });
  
        it('should work correctly when repository does not exist beforehand', async () => {
          const awsRequestStubMap = {
            ...baseAwsRequestStubMap,
            ECR: {
              ...baseAwsRequestStubMap.ECR,
              describeRepositories: describeRepositoriesStub.throws({
                providerError: { code: 'RepositoryNotFoundException' },
              }),
              createRepository: createRepositoryStub.resolves({ repository: { repositoryUri } }),
            },
          };
  
          const { awsNaming, cfTemplate } = await runServerless({
            fixture: 'ecr',
            command: 'package',
            awsRequestStubMap,
            modulesCacheStub,
          });
  
          const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo');
          const functionCfConfig = cfTemplate.Resources[functionCfLogicalId].Properties;
          const versionCfConfig = findVersionCfConfig(cfTemplate.Resources, functionCfLogicalId);
  
          expect(functionCfConfig.Code.ImageUri).to.deep.equal(`${repositoryUri}@sha256:${imageSha}`);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
          expect(describeRepositoriesStub).to.be.calledOnce;
          expect(createRepositoryStub).to.be.calledOnce;
        });
  
        it('should login and retry when docker push fails with no basic auth credentials error', async () => {
          const awsRequestStubMap = {
            ...baseAwsRequestStubMap,
            ECR: {
              ...baseAwsRequestStubMap.ECR,
              describeRepositories: describeRepositoriesStub.resolves({
                repositories: [{ repositoryUri }],
              }),
              createRepository: createRepositoryStub,
            },
          };
          const innerSpawnExtStub = sinon
            .stub()
            .returns({
              stdBuffer: `digest: sha256:${imageSha} size: 1787`,
            })
            .onCall(3)
            .throws({ stdBuffer: 'no basic auth credentials' });
          const {
            awsNaming,
            cfTemplate,
            fixtureData: { servicePath: serviceDir },
          } = await runServerless({
            fixture: 'ecr',
            command: 'package',
            awsRequestStubMap,
            modulesCacheStub: {
              ...modulesCacheStub,
              'child-process-ext/spawn': innerSpawnExtStub,
            },
          });
  
          const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo');
          const functionCfConfig = cfTemplate.Resources[functionCfLogicalId].Properties;
          const versionCfConfig = findVersionCfConfig(cfTemplate.Resources, functionCfLogicalId);
  
          expect(functionCfConfig.Code.ImageUri).to.deep.equal(`${repositoryUri}@sha256:${imageSha}`);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
          expect(describeRepositoriesStub).to.be.calledOnce;
          expect(createRepositoryStub.notCalled).to.be.true;
          expect(innerSpawnExtStub).to.be.calledWith('docker', ['--version']);
          expect(innerSpawnExtStub).to.be.calledWith('docker', [
            'build',
            '-t',
            `${awsNaming.getEcrRepositoryName()}:baseimage`,
            '-f',
            path.join(serviceDir, 'Dockerfile'),
            './',
          ]);
          expect(innerSpawnExtStub).to.be.calledWith('docker', [
            'tag',
            `${awsNaming.getEcrRepositoryName()}:baseimage`,
            `${repositoryUri}:baseimage`,
          ]);
          expect(innerSpawnExtStub).to.be.calledWith('docker', [
            'push',
            `${repositoryUri}:baseimage`,
          ]);
          expect(innerSpawnExtStub).to.be.calledWith('docker', [
            'login',
            '--username',
            'AWS',
            '--password',
            'dockerauthtoken',
            proxyEndpoint,
          ]);
        });
  
        it('should login and retry when docker push fails with token has expired error', async () => {
          const awsRequestStubMap = {
            ...baseAwsRequestStubMap,
            ECR: {
              ...baseAwsRequestStubMap.ECR,
              describeRepositories: describeRepositoriesStub.resolves({
                repositories: [{ repositoryUri }],
              }),
              createRepository: createRepositoryStub,
            },
          };
          const innerSpawnExtStub = sinon
            .stub()
            .returns({
              stdBuffer: `digest: sha256:${imageSha} size: 1787`,
            })
            .onCall(3)
            .throws({ stdBuffer: 'authorization token has expired' });
          await runServerless({
            fixture: 'ecr',
            command: 'package',
            awsRequestStubMap,
            modulesCacheStub: {
              ...modulesCacheStub,
              'child-process-ext/spawn': innerSpawnExtStub,
            },
          });
  
          expect(innerSpawnExtStub).to.be.calledWith('docker', [
            'push',
            `${repositoryUri}:baseimage`,
          ]);
          expect(innerSpawnExtStub).to.be.calledWith('docker', [
            'login',
            '--username',
            'AWS',
            '--password',
            'dockerauthtoken',
            proxyEndpoint,
          ]);
        });
  
        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,
            ECR: {
              ...baseAwsRequestStubMap.ECR,
              describeRepositories: describeRepositoriesStub.resolves({
                repositories: [{ repositoryUri }],
              }),
              createRepository: createRepositoryStub,
            },
          };
          const { awsNaming, cfTemplate } = await runServerless({
            fixture: 'ecr',
            command: 'package',
            awsRequestStubMap,
            modulesCacheStub,
            configExt: {
              provider: {
                ecr: {
                  images: {
                    baseimage: './',
                  },
                },
              },
            },
          });
  
          const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo');
          const functionCfConfig = cfTemplate.Resources[functionCfLogicalId].Properties;
          const versionCfConfig = Object.values(cfTemplate.Resources).find(
            (resource) =>
              resource.Type === 'AWS::Lambda::Version' &&
              resource.Properties.FunctionName.Ref === functionCfLogicalId
          ).Properties;
  
          expect(functionCfConfig.Code.ImageUri).to.deep.equal(`${repositoryUri}@sha256:${imageSha}`);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
          expect(describeRepositoriesStub).to.be.calledOnce;
          expect(createRepositoryStub.notCalled).to.be.true;
        });
  
        it('should work correctly when image is defined with `file` set', async () => {
          const awsRequestStubMap = {
            ...baseAwsRequestStubMap,
            ECR: {
              ...baseAwsRequestStubMap.ECR,
              describeRepositories: describeRepositoriesStub.resolves({
                repositories: [{ repositoryUri }],
              }),
              createRepository: createRepositoryStub,
            },
          };
          const {
            awsNaming,
            cfTemplate,
            fixtureData: { servicePath: serviceDir },
          } = await runServerless({
            fixture: 'ecr',
            command: 'package',
            awsRequestStubMap,
            modulesCacheStub,
            configExt: {
              provider: {
                ecr: {
                  images: {
                    baseimage: {
                      path: './',
                      file: 'Dockerfile.dev',
                    },
                  },
                },
              },
            },
          });
  
          const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo');
          const functionCfConfig = cfTemplate.Resources[functionCfLogicalId].Properties;
          const versionCfConfig = Object.values(cfTemplate.Resources).find(
            (resource) =>
              resource.Type === 'AWS::Lambda::Version' &&
              resource.Properties.FunctionName.Ref === functionCfLogicalId
          ).Properties;
  
          expect(functionCfConfig.Code.ImageUri).to.deep.equal(`${repositoryUri}@sha256:${imageSha}`);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
          expect(describeRepositoriesStub).to.be.calledOnce;
          expect(createRepositoryStub.notCalled).to.be.true;
          expect(spawnExtStub).to.be.calledWith('docker', [
            'build',
            '-t',
            `${awsNaming.getEcrRepositoryName()}:baseimage`,
            '-f',
            path.join(serviceDir, 'Dockerfile.dev'),
            './',
          ]);
        });
  
        it('should work correctly when image is defined with `cacheFrom` set', async () => {
          const awsRequestStubMap = {
            ...baseAwsRequestStubMap,
            ECR: {
              ...baseAwsRequestStubMap.ECR,
              describeRepositories: describeRepositoriesStub.resolves({
                repositories: [{ repositoryUri }],
              }),
              createRepository: createRepositoryStub,
            },
          };
          const {
            awsNaming,
            cfTemplate,
            fixtureData: { servicePath: serviceDir },
          } = await runServerless({
            fixture: 'ecr',
            command: 'package',
            awsRequestStubMap,
            modulesCacheStub,
            configExt: {
              provider: {
                ecr: {
                  images: {
                    baseimage: {
                      path: './',
                      file: 'Dockerfile.dev',
                      cacheFrom: ['my-image:latest'],
                    },
                  },
                },
              },
            },
          });
  
          const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo');
          const functionCfConfig = cfTemplate.Resources[functionCfLogicalId].Properties;
          const versionCfConfig = Object.values(cfTemplate.Resources).find(
            (resource) =>
              resource.Type === 'AWS::Lambda::Version' &&
              resource.Properties.FunctionName.Ref === functionCfLogicalId
          ).Properties;
  
          expect(functionCfConfig.Code.ImageUri).to.deep.equal(`${repositoryUri}@sha256:${imageSha}`);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
          expect(describeRepositoriesStub).to.be.calledOnce;
          expect(createRepositoryStub.notCalled).to.be.true;
          expect(spawnExtStub).to.be.calledWith('docker', [
            'build',
            '-t',
            `${awsNaming.getEcrRepositoryName()}:baseimage`,
            '-f',
            path.join(serviceDir, 'Dockerfile.dev'),
            '--cache-from',
            'my-image:latest',
            './',
          ]);
        });
  
        it('should work correctly when image is defined with `buildArgs` set', async () => {
          const awsRequestStubMap = {
            ...baseAwsRequestStubMap,
            ECR: {
              ...baseAwsRequestStubMap.ECR,
              describeRepositories: describeRepositoriesStub.resolves({
                repositories: [{ repositoryUri }],
              }),
              createRepository: createRepositoryStub,
            },
          };
          const {
            awsNaming,
            cfTemplate,
            fixtureData: { servicePath: serviceDir },
          } = await runServerless({
            fixture: 'ecr',
            command: 'package',
            awsRequestStubMap,
            modulesCacheStub,
            configExt: {
              provider: {
                ecr: {
                  images: {
                    baseimage: {
                      path: './',
                      file: 'Dockerfile.dev',
                      buildArgs: {
                        TESTKEY: 'TESTVAL',
                      },
                    },
                  },
                },
              },
            },
          });
  
          const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo');
          const functionCfConfig = cfTemplate.Resources[functionCfLogicalId].Properties;
          const versionCfConfig = Object.values(cfTemplate.Resources).find(
            (resource) =>
              resource.Type === 'AWS::Lambda::Version' &&
              resource.Properties.FunctionName.Ref === functionCfLogicalId
          ).Properties;
  
          expect(functionCfConfig.Code.ImageUri).to.deep.equal(`${repositoryUri}@sha256:${imageSha}`);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
          expect(describeRepositoriesStub).to.be.calledOnce;
          expect(createRepositoryStub.notCalled).to.be.true;
          expect(spawnExtStub).to.be.calledWith('docker', [
            'build',
            '-t',
            `${awsNaming.getEcrRepositoryName()}:baseimage`,
            '-f',
            path.join(serviceDir, 'Dockerfile.dev'),
            '--build-arg',
            'TESTKEY=TESTVAL',
            './',
          ]);
        });
  
        it('should work correctly when `functions[].image` is defined with explicit name', async () => {
          const awsRequestStubMap = {
            ...baseAwsRequestStubMap,
            ECR: {
              ...baseAwsRequestStubMap.ECR,
              describeRepositories: describeRepositoriesStub.resolves({
                repositories: [{ repositoryUri }],
              }),
              createRepository: createRepositoryStub,
            },
          };
          const { awsNaming, cfTemplate } = await runServerless({
            fixture: 'ecr',
            command: 'package',
            awsRequestStubMap,
            modulesCacheStub,
            configExt: {
              provider: {
                ecr: {
                  images: {
                    baseimage: './',
                  },
                },
              },
              functions: {
                foo: {
                  image: {
                    name: 'baseimage',
                  },
                },
              },
            },
          });
  
          const functionCfLogicalId = awsNaming.getLambdaLogicalId('foo');
          const functionCfConfig = cfTemplate.Resources[functionCfLogicalId].Properties;
          const versionCfConfig = findVersionCfConfig(cfTemplate.Resources, functionCfLogicalId);
          expect(functionCfConfig.Code.ImageUri).to.deep.equal(`${repositoryUri}@sha256:${imageSha}`);
          expect(versionCfConfig.CodeSha256).to.equal(imageSha);
        });
  
        it('should fail when docker command is not available', async () => {
          await expect(
            runServerless({
              fixture: 'ecr',
              command: 'package',
              awsRequestStubMap: baseAwsRequestStubMap,
              modulesCacheStub: {
                'child-process-ext/spawn': sinon.stub().throws(),
              },
            })
          ).to.be.eventually.rejected.and.have.property('code', 'DOCKER_COMMAND_NOT_AVAILABLE');
        });
  
        it('should fail when docker build fails', async () => {
          await expect(
            runServerless({
              fixture: 'ecr',
              command: 'package',
              awsRequestStubMap: baseAwsRequestStubMap,
              modulesCacheStub: {
                ...modulesCacheStub,
                'child-process-ext/spawn': sinon.stub().returns({}).onSecondCall().throws(),
              },
            })
          ).to.be.eventually.rejected.and.have.property('code', 'DOCKER_BUILD_ERROR');
        });
  
        it('should fail when docker tag fails', async () => {
          await expect(
            runServerless({
              fixture: 'ecr',
              command: 'package',
              awsRequestStubMap: baseAwsRequestStubMap,
              modulesCacheStub: {
                ...modulesCacheStub,
                'child-process-ext/spawn': sinon.stub().returns({}).onCall(2).throws(),
              },
            })
          ).to.be.eventually.rejected.and.have.property('code', 'DOCKER_TAG_ERROR');
        });
  
        it('should fail when docker push fails', async () => {
          await expect(
            runServerless({
              fixture: 'ecr',
              command: 'package',
              awsRequestStubMap: baseAwsRequestStubMap,
              modulesCacheStub: {
                ...modulesCacheStub,
                'child-process-ext/spawn': sinon.stub().returns({}).onCall(3).throws(),
              },
            })
          ).to.be.eventually.rejected.and.have.property('code', 'DOCKER_PUSH_ERROR');
        });
  
        it('should fail when docker login fails', async () => {
          await expect(
            runServerless({
              fixture: 'ecr',
              command: 'package',
              awsRequestStubMap: baseAwsRequestStubMap,
              modulesCacheStub: {
                ...modulesCacheStub,
                'child-process-ext/spawn': sinon
                  .stub()
                  .returns({})
                  .onCall(3)
                  .throws({ stdBuffer: 'no basic auth credentials' })
                  .onCall(4)
                  .throws(),
              },
            })
          ).to.be.eventually.rejected.and.have.property('code', 'DOCKER_LOGIN_ERROR');
        });
      });
    });
  });
  