Home Manual Reference Source Test

test/AtSCMCli.spec.js

import { join } from 'path';
import expect from 'unexpected';
import { spy, stub } from 'sinon';
import proxyquire from 'proxyquire';
import colors from 'chalk';
import Liftoff from 'liftoff';
import { LogFormat } from '../src/lib/util/Logger';
import UsageError from '../src/lib/error/UsageError';
import Command from '../src/lib/cli/Command';
import pkg from '../package.json';

const LoggerSpy = {
  debug: spy(),
  info: spy(),
  warn: spy(),
  error: spy(),
  applyOptions: spy(),
  colors,
  format: LogFormat,
};

const AtSCMCli = proxyquire('../src/AtSCMCli', {
  './lib/util/Logger': {
    _esModule: true,
    default: LoggerSpy,
  },
}).default;

/** @test {AtSCMCli} */
describe('AtSCMCli', function () {
  beforeEach(() => {
    LoggerSpy.debug.resetHistory();
    LoggerSpy.info.resetHistory();
    LoggerSpy.warn.resetHistory();
    LoggerSpy.error.resetHistory();
    LoggerSpy.applyOptions.resetHistory();
  });

  /** @test {AtSCMCli#constructor} */
  describe('#constructor', function () {
    it('should throw UsageError with invalid options', function () {
      expect(() => new AtSCMCli(['--cwd']), 'to throw');
    });

    it('should create an instance of Liftoff', function () {
      expect(new AtSCMCli(), 'to be a', Liftoff);
    });

    it('should not add run argument if valid command is given', function () {
      expect(new AtSCMCli(['docs'])._argv, 'to equal', ['docs']);
    });

    it('should not add run argument if --help is given', function () {
      expect(new AtSCMCli(['--help'])._argv, 'to equal', ['--help']);
    });

    it('should not add run argument if --version is given', function () {
      expect(new AtSCMCli(['--version'])._argv, 'to equal', ['--version']);
    });

    it('should add run argument if no command is given', function () {
      expect(new AtSCMCli()._argv, 'to equal', ['run']);
    });

    it('should set runViaCli', function () {
      // runViaCli is always set to false as we don't run this test from the command line directly.
      expect(new AtSCMCli().runViaCli, 'to equal', false);
    });
  });

  /** @test {AtSCMCli#_exposeOverride} */
  describe('#_exposeOverride', function () {
    it('should set single options', function () {
      AtSCMCli.prototype._exposeOverride({ test: 13 }, 'test', 'TEST_ENVS__');

      expect(process.env.TEST_ENVS__TEST, 'to be defined');
      expect(process.env.TEST_ENVS__TEST, 'to equal', '13');
    });

    it('should set object options', function () {
      AtSCMCli.prototype._exposeOverride({ test: { another: 13 } }, 'test', 'TEST_ENVS__');

      expect(process.env.TEST_ENVS__TEST__ANOTHER, 'to be defined');
      expect(process.env.TEST_ENVS__TEST__ANOTHER, 'to equal', '13');
    });

    it('should use default base', function () {
      AtSCMCli.prototype._exposeOverride({ test: 13 }, 'test');

      expect(process.env.ATSCM_PROJECT__TEST, 'to be defined');
      expect(process.env.ATSCM_PROJECT__TEST, 'to equal', '13');

      delete process.env.ATSCM_PROJECT__TEST;
    });
  });

  /** @test {AtSCMCli#parseArguments} */
  describe('#parseArguments', function () {
    const unknownArgCli = new AtSCMCli(['config', '--unknown']);
    const unknownRunArgCli = new AtSCMCli(['run', '--unknown']);

    it('should fail with UsageError with an unknown argument', function () {
      return expect(unknownArgCli.parseArguments(), 'when rejected', 'to be a', UsageError);
    });

    it('should report unknown arguments for strict commands', function () {
      return expect(
        unknownArgCli.parseArguments(),
        'when rejected',
        'to have message',
        'Unknown argument: unknown'
      );
    });

    it('should not report unknown arguments for non-strict commands', function () {
      return expect(unknownRunArgCli.parseArguments(), 'when fulfilled', 'to have properties', {
        unknown: true,
      });
    });

    it('should return options with valid arguments', function () {
      return expect(
        new AtSCMCli(['docs', '--cli']).parseArguments(),
        'when fulfilled',
        'to have properties',
        { _: ['docs'], cli: true }
      );
    });

    it('should set AtSCMCli#options with valid arguments', function () {
      const cli = new AtSCMCli(['docs', '--cli']);

      return cli.parseArguments().then((opts) => expect(cli.options, 'to equal', opts));
    });

    it('should set AtSCMCli#command with valid command', function () {
      const cli = new AtSCMCli(['docs', '--cli']);

      return cli.parseArguments().then(() => expect(cli.command.name, 'to equal', 'docs'));
    });

    it('should expose project options as environment variables', function () {
      const cli = new AtSCMCli(['--tasks', '--project.test', 'test']);

      return cli
        .parseArguments()
        .then(() => expect(process.env.ATSCM_PROJECT__TEST, 'to equal', 'test'));
    });
  });

  /** @test {AtSCMCli#getEnvironment} */
  describe('#getEnvironment', function () {
    beforeEach(() => spy(Liftoff.prototype, 'launch'));
    afterEach(() => Liftoff.prototype.launch.restore());

    const cli = new AtSCMCli([
      '--cwd',
      'test/fixtures',
      '--projectfile',
      'test/fixtures/Atviseproject.js',
    ]);

    it('should call Liftoff#launch with cwd and projectfile option', function () {
      return cli.getEnvironment().then(() => {
        expect(Liftoff.prototype.launch.calledOnce, 'to be', true);
        expect(Liftoff.prototype.launch.lastCall.args[0], 'to equal', {
          cwd: 'test/fixtures',
          configPath: 'test/fixtures/Atviseproject.js',
        });
      });
    });

    it('should set AtSCMCli#environment', function () {
      return cli.getEnvironment().then((env) => {
        expect(cli.environment, 'to be defined');
        expect(cli.environment, 'to equal', env);
        expect(cli.environment, 'to have keys', [
          'cwd',
          'configPath',
          'configBase',
          'modulePath',
          'modulePackage',
        ]);
      });
    });

    context('when not looking up parent directories', function () {
      it('should resolve to initial cwd', function () {
        return new AtSCMCli()
          .getEnvironment(false)
          .then((env) => expect(env.cwd, 'to equal', process.cwd()));
      });

      const projChildDir = join('test/fixtures/node_modules');

      it('should resolve to initial cwd in project child directories', function () {
        return new AtSCMCli(['--cwd', projChildDir])
          .getEnvironment(false)
          .then((env) => expect(env.cwd, 'to end with', projChildDir));
      });

      it('should ignore --projectfile option', function () {
        return new AtSCMCli([
          '--cwd',
          projChildDir,
          '--projectfile',
          join(projChildDir, '../Atviseproject.js'),
        ])
          .getEnvironment(false)
          .then((env) => expect(env.cwd, 'to end with', projChildDir));
      });
    });
  });

  /** @test {AtSCMCli#requireEnvironment} */
  describe('#requireEnvironment', function () {
    it('should fail without local module', function () {
      return expect(
        new AtSCMCli().requireEnvironment(),
        'to be rejected with',
        /Local .* not found/
      );
    });

    it('should return environment if successful', function () {
      const cli = new AtSCMCli(['--cwd', 'test/fixtures']);

      return expect(
        cli.requireEnvironment(),
        'when fulfilled',
        'to have values satisfying',
        'not to be falsy'
      );
    });
  });

  /** @test {AtSCMCli#getVersion} */
  describe('#getVersion', function () {
    it('should return cli version without local module', function () {
      return expect(new AtSCMCli().getVersion(), 'when fulfilled', 'to equal', {
        cli: pkg.version,
      });
    });

    it('should even return cli version with invalid cwd', function () {
      return expect(
        new AtSCMCli(['--cwd', 'invalid/path']).getVersion(),
        'when fulfilled',
        'to equal',
        { cli: pkg.version }
      );
    });

    it('should return local version with local module', function () {
      return expect(new AtSCMCli(['--cwd', 'test/fixtures']).getVersion(), 'to be fulfilled with', {
        cli: pkg.version,
        local: 'latest',
      });
    });
  });

  /** @test {AtSCMCli#printVersion} */
  describe('#printVersion', function () {
    it('should print cli version only without local module', function () {
      return expect(new AtSCMCli().printVersion(), 'to be fulfilled').then(() => {
        expect(LoggerSpy.info.calledOnce, 'to be', true);
        expect(LoggerSpy.info.lastCall.args[0], 'to match', /atscm-cli/);
        expect(LoggerSpy.info.lastCall.args[1], 'to match', new RegExp(pkg.version));
      });
    });

    it('should print cli, local and atserver version with local module', async function () {
      await expect(new AtSCMCli(['--cwd', 'test/fixtures']).printVersion(), 'to be fulfilled');

      expect(LoggerSpy.info.calledThrice, 'to be', true);

      expect(LoggerSpy.info.getCall(1).args, 'to satisfy', [/atscm/, /latest/]);
      expect(LoggerSpy.info.getCall(2).args, 'to satisfy', [/atvise server/, /3.4.0/]);
    });
  });

  /** @test {AtSCMCli#runCommand} */
  describe('#runCommand', function () {
    it('should print version if --version is used', function () {
      const cli = new AtSCMCli(['--version']);
      cli.options.version = true;

      spy(cli, 'printVersion');

      return cli.runCommand().then(() => {
        expect(cli.printVersion.calledOnce, 'to be', true);
      });
    });

    it('should run command if used', function () {
      const command = new Command('cmd', 'Just testing');
      stub(command, 'run');
      const cli = new AtSCMCli(['--cwd', 'test/fixtures']);
      cli.command = command;

      return cli.runCommand().then(() => {
        expect(command.run.calledOnce, 'to be true');
      });
    });

    it('should warn if no command is used', function () {
      const cli = new AtSCMCli(['--cwd', 'test/fixtures']);

      return cli.runCommand().then(() => {
        expect(LoggerSpy.warn.calledOnce, 'to be', true);
        expect(LoggerSpy.warn.lastCall.args[0], 'to contain', 'No command specified');
      });
    });
  });

  /** @test {AtSCMCli#launch} */
  describe('#launch', function () {
    it('should call AtSCMCli#parseArguments', function () {
      const cli = new AtSCMCli();
      spy(cli, 'parseArguments');
      stub(cli, 'runCommand').callsFake(() => Promise.resolve(true));

      return cli.launch().then(() => {
        expect(cli.runCommand.calledOnce, 'to be true');
        expect(cli.parseArguments.calledOnce, 'to be', true);
      });
    });

    it('should handle all exceptions if run via cli', function () {
      const cli = new AtSCMCli();
      cli.runViaCli = true;
      stub(cli, 'runCommand').callsFake(() => Promise.reject(new Error('test')));

      return expect(cli.launch(), 'to be fulfilled');
    });

    it('should report help on usage errors if run via cli', function () {
      const cli = new AtSCMCli(['--unknown']);
      cli.runViaCli = true;

      return expect(cli.launch(), 'to be fulfilled');
    });

    it('should start debug mode with `--debug` option', function () {
      const cli = new AtSCMCli(['--debug']);
      cli.runViaCli = true;

      return expect(cli.launch(), 'to be fulfilled').then(() =>
        expect(process.env.ATSCM_DEBUG, 'to equal', 'true')
      );
    });

    context('with ATSCM_DEBUG env var', function () {
      before(() => {
        process.env.ATSCM_DEBUG = 'yes';
      });
      after(() => {
        process.env.ATSCM_DEBUG = undefined;
      });

      it('should start debug mode', function () {
        const cli = new AtSCMCli(['--debug']);
        cli.runViaCli = true;

        return expect(cli.launch(), 'to be fulfilled').then(() =>
          expect(process.env.ATSCM_DEBUG, 'to equal', 'yes')
        );
      });
    });
  });
});