zalando/zappr

View on GitHub
server/handler/CheckHandler.js

Summary

Maintainability
A
0 mins
Test Coverage
import Sequelize from 'sequelize'
import CheckHandlerError, { CHECK_NOT_FOUND, CHECK_EXISTS, DATABASE_ERROR } from './CheckHandlerError'
import { Check } from '../model'
import { githubService } from '../service/GithubService'
import { getCheckByType } from '../checks'
import { logger } from "../../common/debug"

const debug = logger('check-handler')

/**
 * @param {Array.<String>} types - Zappr check types
 * @returns {Array.<String>} Github event names
 */
function findHookEventsFor(types) {
  return types.map(type => {
                const check = getCheckByType(type)
                if (!check) throw new CheckHandlerError(CHECK_NOT_FOUND, {type})
                return check.HOOK_EVENTS
              })
              .reduce((arr, evts) => arr.concat(evts), [])         // flatten
              .filter((evt, i, arr) => i === arr.lastIndexOf(evt)) // deduplicate
}

export class CheckHandler {
  constructor(github = githubService) {
    this.github = github
  }

  /**
   * @param {Number} repoId - Repository ID
   * @param {String} type - Check type
   * @param {String} user - The user who enabled it
   * @param {String} token - Authentication token
   * @returns {Promise.<Check>}
   * @throws {CheckHandlerError}
   */
  async onCreateCheck(repoId, type, user, token) {
    debug(`create check ${type} for repo ${repoId} w/ token ${token ? token.substr(0, 4) : 'NONE'} by user ${user}`)
    try {
      return await Check.create({
        repositoryId: repoId,
        type,
        token,
        created_by: user
      }, {
        attributes: {exclude: ['token']}
      })
    } catch (e) {
      throw new CheckHandlerError(CHECK_EXISTS, {type, repository: repoId})
    }
  }

  async onRefreshTokens(repoId, user) {
    const token = user.accessToken;
    const userName = user.json.login;
    debug(`refreshing token for all checks for repo ${repoId} w/ token ${token ? token.substr(0, 4) : 'NONE'} by user ${userName} `)
    try {
      return await Check.update({ token: token, created_by: userName },
        {
          where: {
            repositoryId: repoId
          },
          returning: true,
          individualHooks: true
        });
    } catch(e) {
      throw new CheckHandlerError(REFRESH_TOKEN_ERROR, {repository: repoId}) 
    }
  }


  /**
   * @param {Number} repoId - Repository ID
   * @param {String} type - Check type
   * @returns {Promise.<Check>}
   * @throws {CheckHandlerError}
   */
  async onGetOne(repoId, type) {
    debug(`find Check ${type} for repo ${repoId}`)
    let check
    try {
      check = await Check.findOne({
        where: {
          repositoryId: repoId,
          type
        }
      })
    } catch (e) {
      throw new CheckHandlerError(DATABASE_ERROR, {type, repository: repoId})
    }
    if (!check) throw new CheckHandlerError(CHECK_NOT_FOUND, {type, repository: repoId})
    return check
  }

  /**
   * @param {Number} repoId - Repository ID
   * @param {String} type - Check type
   * @returns {Promise.<Number>} - Number of destroyed rows
   * @throws {CheckHandlerError}
   */
  async onDeleteCheck(repoId, type) {
    debug(`delete check ${type} for repo ${repoId}`)
    let check
    try {
      await Check.destroy({
        where: {
          repositoryId: repoId,
          type
        }
      })
    } catch (e) {
      throw new CheckHandlerError(DATABASE_ERROR, {type, repository: repoId})
    }
  }

  /**
   * @param {object} user - Current user
   * @param {Repository} repository - Repository to enable Check for
   * @param {String} type - Check type
   * @returns {Promise.<Check>}
   * @throws {CheckHandlerError}
   */
  async onEnableCheck(user, repository, type) {
    const repo = repository.get('json')
    const types = [type, ...repository.checks.map(c => c.type)]
    const events = findHookEventsFor(types)
    // TODO: could use a database constraint instead?
    let existingCheck
    try {
      existingCheck = await checkHandler.onGetOne(repo.id, type)
    } catch (e) {
      // Expect check not to exist
    }
    if (existingCheck) throw new CheckHandlerError(CHECK_EXISTS, {type, repository: repo.id})

    await this.github.updateWebhookFor(repo.owner.login, repo.name, events, user.accessToken)
    const check = await checkHandler.onCreateCheck(repo.id, type, user.json.login, user.accessToken)
    debug(`${repo.full_name}: enabled check ${type}`)
    return check
  }

  /**
   * @param {object} user - Current user
   * @param {Repository} repository - Repository to disable Check for
   * @param {String} type - Check type
   * @returns {Promise}
   * @throws {CheckHandlerError}
   */
  async onDisableCheck(user, repository, type) {
    const repo = repository.get('json')
    const types = repository.checks.map(c => c.type).filter(t => t !== type)
    const evts = findHookEventsFor(types)
    await this.github.updateWebhookFor(repo.owner.login, repo.name, evts, user.accessToken);
    await this.onDeleteCheck(repo.id, type)
    debug(`${repo.full_name}: disabled check ${type}`)
  }

  onExecutionStart(repoId, type, delay) {
    // set last_execution_ts to now
    // set last execution delay
    return Check.update({
      last_execution_ts: Sequelize.fn('NOW'),
      last_execution_delay_ms: delay,
      last_execution_successful: null,
      last_execution_ms: null
    }, {
      where: {
        repositoryId: repoId,
        type
      }
    })
  }

  onExecutionEnd(repoId, type, executionTime, executionSuccess) {
    // set last execution ms
    // set last execution success = true
    return Check.update({
      last_execution_successful: executionSuccess,
      last_execution_ms: executionTime
    }, {
      where: {
        repositoryId: repoId,
        type
      }
    })
  }
}

export const checkHandler = new CheckHandler()