ssube/isolex

View on GitHub
test/TestApp.ts

Summary

Maintainability
C
1 day
Test Coverage
import { defer, getTestLogger, SIGNAL_RELOAD, SIGNAL_RESET, SIGNAL_STOP, timeout } from '@apextoaster/js-utils';
import { expect } from 'chai';
import { ineeda } from 'ineeda';
import { defaultTo } from 'lodash';
import { BaseError, LogLevel } from 'noicejs';
import { stub } from 'sinon';

import { createBot, CreateOptions, ExitStatus, main, runBot } from '../src/app';
import { Bot, BotDefinition } from '../src/Bot';
import { ServiceEvent } from '../src/Service';
import { serviceSpy } from './helpers/container';

const MAX_SIGNAL_TIME = 100; // ms
const MAX_START_TIME = 750; // ms

const MAIN_START_TIME = 1000; // ms
const MAIN_STOP_TIME = 500;

const TEST_SERVICE = 'test-service';
const TEST_CONFIG: BotDefinition = {
  data: {
    controllers: [],
    endpoints: [],
    generators: [],
    listeners: [],
    locale: {
      data: {
        lang: 'en',
      },
      metadata: {
        kind: 'locale',
        name: 'default-locale',
      },
    },
    logger: {
      level: LogLevel.Warn,
      name: 'test-logger',
    },
    modules: [],
    parsers: [],
    process: {
      pid: {
        file: 'test.pid',
      },
    },
    services: {
      timeout: MAX_SIGNAL_TIME,
    },
    storage: {
      data: {
        migrate: false,
        orm: {
          database: './out/test.db',
          type: 'sqlite',
        },
      },
      metadata: {
        kind: 'storage',
        name: 'typeorm',
      },
    },
  },
  metadata: {
    kind: 'bot',
    name: 'test-bot',
  },
};

const TEST_CONFIG_SERVICE: CreateOptions = {
  config: {
    data: {
      ...TEST_CONFIG.data,
      controllers: [{
        data: {
          filters: [],
          redirect: {
            defaults: {},
            forces: {},
          },
          strict: true,
          transforms: [],
        },
        metadata: {
          kind: TEST_SERVICE,
          name: TEST_SERVICE,
        },
      }],
    },
    metadata: TEST_CONFIG.metadata,
  },
  logger: getTestLogger(),
};

const TEST_ARGS_VALID = [
  '--config-name',
  'test-valid.yml',
  '--config-path',
  defaultTo(process.env.DOCS_PATH, __dirname),
];

const TEST_ARGS_INVALID = [
  '--config-name',
  'test-invalid.yml',
  '--config-path',
  defaultTo(process.env.DOCS_PATH, __dirname),
];

describe('app bot stuff', async () => {
  it('should create a bot and container', async () => {
    const { bot } = await createBot({
      config: TEST_CONFIG,
      logger: getTestLogger(),
    });

    expect(bot).to.be.an.instanceOf(Bot);
  });

  it('should run a bot', async () => {
    const options = {
      config: TEST_CONFIG,
      logger: getTestLogger(),
    };
    const { bot } = await createBot(options);

    const pendingStatus = runBot(options, bot);
    await Promise.race([
      pendingStatus,
      defer(MAX_START_TIME),
    ]); // wait for the bot to start up

    process.kill(process.pid, SIGNAL_STOP); // ask it to stop
    const status = await pendingStatus;

    expect(status).to.equal(ExitStatus.Success);
  });

  /* tslint:disable:no-identical-functions */
  it('should reset metrics while running', async () => {
    const { bot, ctr } = await createBot(TEST_CONFIG_SERVICE);
    const { svc, spies } = await serviceSpy({});

    const [module] = ctr.getModules();
    module.bind(TEST_SERVICE).toInstance(svc);

    const pendingStatus = runBot(TEST_CONFIG_SERVICE, bot);
    await Promise.race([
      pendingStatus,
      defer(MAX_START_TIME),
    ]); // wait for the bot to start up

    process.kill(process.pid, SIGNAL_RESET); // reset metrics
    await Promise.race([
      pendingStatus,
      defer(MAX_SIGNAL_TIME),
    ]);

    process.kill(process.pid, SIGNAL_STOP); // ask it to stop
    const status = await pendingStatus;

    const EXPECTED_SIGNALS = 3;
    expect(status).to.equal(ExitStatus.Success);
    expect(spies.notify).to.have.callCount(EXPECTED_SIGNALS)
      .and.been.calledWith(ServiceEvent.Start)
      .and.been.calledWith(ServiceEvent.Reset)
      .and.been.calledWith(ServiceEvent.Stop);
  });

  it('should reload config while running', async () => {
    const { bot, ctr } = await createBot(TEST_CONFIG_SERVICE);
    const { svc, spies } = await serviceSpy({});

    const [module] = ctr.getModules();
    module.bind(TEST_SERVICE).toInstance(svc);

    const pendingStatus = runBot(TEST_CONFIG_SERVICE, bot);
    await Promise.race([
      pendingStatus,
      defer(MAX_START_TIME),
    ]); // wait for the bot to start up

    process.kill(process.pid, SIGNAL_RELOAD); // reset metrics
    await Promise.race([
      pendingStatus,
      defer(MAX_SIGNAL_TIME),
    ]);

    process.kill(process.pid, SIGNAL_STOP); // ask it to stop
    const status = await pendingStatus;

    const EXPECTED_SIGNALS = 3;
    expect(status).to.equal(ExitStatus.Success);
    expect(spies.notify).to.have.callCount(EXPECTED_SIGNALS)
      .and.been.calledWith(ServiceEvent.Start)
      .and.been.calledWith(ServiceEvent.Reload)
      .and.been.calledWith(ServiceEvent.Stop);
  });

  it('should handle errors starting the bot', async () => {
    const start = stub().throws(BaseError);
    const bot = ineeda<Bot>({
      start,
    });
    return expect(runBot({
      config: TEST_CONFIG,
      logger: getTestLogger(),
    }, bot)).to.eventually.equal(ExitStatus.Error);
  });
});

/* tslint:disable:no-unbound-method */
describe('main', async () => {
  it('should return exit status', async () => {
    const pendingStatus = main(TEST_ARGS_VALID);
    await defer(MAIN_START_TIME);

    process.kill(process.pid, SIGNAL_STOP); // ask it to stop
    const status = await timeout(MAIN_STOP_TIME, pendingStatus);

    expect(status).to.equal(ExitStatus.Success, 'exit status should be set and successful');
  });

  it('should validate the config before starting', async () => {
    const status = await main(TEST_ARGS_INVALID);

    expect(status).to.equal(ExitStatus.Error);
  });

  it('should test config without starting', async () => {
    const status = await main([
      ...TEST_ARGS_VALID,
      '--test',
    ]);

    expect(status).to.equal(ExitStatus.Success);
  });

  it('should fail when testing invalid config', async () => {
    const status = await main([
      ...TEST_ARGS_INVALID,
      '--test',
    ]);

    expect(status).to.equal(ExitStatus.Error);
  });
});