├── .editorconfig ├── .eslintrc ├── .github ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── create-issue.html ├── env ├── package.json ├── src ├── app.js ├── github.js ├── logger.js ├── modules │ ├── issues │ │ ├── autoAssign.js │ │ ├── autoLabel.js │ │ ├── replyInvalid.js │ │ └── replyNeedDemo.js │ ├── pull_request │ │ ├── autoReviewRequest.js │ │ ├── replyInvalidTitle.js │ │ └── titlePrefixToLabel.js │ └── releases │ │ └── autoReleaseNote.js └── utils.js └── test ├── github.js ├── modules └── issues │ ├── autoAssign.js │ └── autoLabel.js ├── utils.js └── utils └── clean.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | { 3 | "root": true, 4 | "env": { 5 | "node": true 6 | }, 7 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 8 | "extends": "standard", 9 | // add your custom rules here 10 | "rules": { 11 | // allow paren-less arrow functions 12 | "arrow-parens": 0, 13 | // allow async-await 14 | "generator-star-spacing": 0, 15 | // allow debugger during development 16 | "no-debugger": 2 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 用户贡献指南 2 | 3 | 非常感谢您关注 [@xuexb/github-bot](https://github.com/xuexb/github-bot) 项目,在提交您的贡献之前,请务必认真阅读以下准则。 4 | 5 | 1. [问题反馈](#issue) 6 | 1. [开发指南](#develop) 7 | 1. [本地调试](#debug) 8 | 1. [常用脚本命令( npm scripts )](#npm-scripts) 9 | 1. [代码风格](#code-spec) 10 | 1. [项目结构](#dir-spec) 11 | 1. [提交请求(pull request)](#pull-request) 12 | 1. [提交信息规范](#commit-message-spec) 13 | 1. [后记](#open-source) 14 | 15 | 16 | ## 问题反馈 17 | 18 | 1. 请避免提交重复的 issue,在提交之前搜索现有的 issue 。 19 | 1. 请使用 [创建 issue](https://xuexb.github.io/github-bot/create-issue.html) 页面反馈问题,否则将直接被关闭。 20 | 21 | 22 | ## 开发设置 23 | 24 | 需要安装 [nodejs](https://nodejs.org) 版本7.8.0+ ,下载项目到本地后安装依赖 `npm install` ,安装完成后将自动添加 git commit 提交信息格式检查和提交前代码格式验证。 25 | 26 | 27 | ### 本地调试 28 | 29 | 目前是基于 [github api](https://developer.github.com/v3/) + 接口说明 + 在线 github webhook 实时触发调试,计划添加一个 mock 服务,支持在本地实时的调试代码功能。 30 | 31 | 32 | ### 常用脚本命令( npm scripts ) 33 | 34 | ``` bash 35 | # 基于 koa 启动本地服务器接口 36 | $ npm run start 37 | 38 | # 使用 eslint 验证代码风格 39 | $ npm run lint 40 | ``` 41 | 42 | 43 | ### 代码风格 44 | 45 | 使用 es6 开发,基于 编写代码,基于 [eslint](https://eslint.org/) 验证代码格式。 46 | 47 | 48 | ### 项目结构 49 | 50 | ``` 51 | . 52 | ├── create-issue.html - 创建 issue 页面,会往内容里注入一些特殊的标识用来让 bot 判断是否规范 53 | ├── env - 环境配置模板 54 | └── src 55 | ├── app.js - 服务启动入口 56 | ├── github.js - 基于 https://github.com/octokit/node-github + async 完成接口对外统一 57 | ├── modules - 以功能为目录区分形成模块 58 | │ ├── issues - issue 59 | │ │ ├── autoAssign.js - 根据配置自动 assign 给指定的人 60 | │ │ ├── autoLabel.js - 根据 create-issue.html 页面注入的标记自动给 issue 打对应的 label 61 | │ │ ├── replyInvalid.js - 判断是否有 create-issue.html 页面注入的标记,不存在则自动关闭 62 | │ │ └── replyNeedDemo.js - 回复需要相关 demo 链接 63 | │ ├── pull_request - PR 64 | │ │ ├── autoReviewRequest.js - 根据配置自动 reviewer 给指定的人 65 | │ │ ├── replyInvalidTitle.js - 根据标题规范自动提醒需要修改 66 | │ │ └── titlePrefixToLabel.js - 根据标题规范化前缀和配置,自动打上 label 67 | │ └── releases - releases 68 | │ └── autoReleaseNote.js - 添加新 tag 时自动根据距离上一个 tag 的 commit log 自动归类,发布 releases notes 69 | └── utils.js - 常用工具方法 70 | ``` 71 | 72 | 73 | ### 提交请求(pull request) 74 | 75 | 1. fork [@xuexb/github-bot](https://github.com/xuexb/github-bot) 76 | 1. 把个人仓库(repository)克隆到电脑上,并安装所依赖的插件。 77 | 1. 开始编辑,并自测通过(后续Todo: 添加测试用例)后提前代码。 78 | 1. 推送(push)分支。 79 | 1. 建立一个新的合并申请(pull request)并描述变动。 80 | 81 | 82 | ## 提交信息规范 83 | 84 | git commit 信息和 pull request 标题必须遵循 [commit-log-和-pr-标题规则](https://github.com/xuexb/github-bot#commit-log-和-pr-标题规则) ,否则不予合入。 85 | 86 | 87 | ## 后记 88 | 89 | 感谢您的贡献, github-bot 因您而更完善。 90 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | log 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # Typescript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | fis-conf.js 62 | /github 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | cache: 5 | directories: 6 | - node_modules 7 | script: 8 | - npm run lint 9 | - npm run test:cov 10 | after_script: "npm install coveralls && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 前端小武 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github-bot 2 | 3 | github 机器人:在服务端上启动一个基于 [koajs](http://koajs.com/) 的 http server ,建立一些项目的规范(如 issue 格式、 pull request 格式、配置一些指定 label 根据的 owner 、统一 git commit log 格式等),基于 [github webhooks](https://developer.github.com/webhooks/) 和 [github api](https://developer.github.com/v3/) 让机器人(通常是一个单独的帐号,如 [@jiandansousuo-bot](https://github.com/jiandansousuo-bot) )自动处理一些事情,从而达到快速响应、自动化、解放人力的效果。 4 | 5 | [![Build Status](https://travis-ci.org/xuexb/github-bot.svg?branch=master)](https://travis-ci.org/xuexb/github-bot) 6 | [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com) 7 | [![Test Coverage](https://img.shields.io/coveralls/xuexb/github-bot/master.svg)](https://coveralls.io/r/xuexb/github-bot?branch=master) 8 | 9 | ## 声明 10 | 11 | 该 [仓库@xuexb/github-bot](https://github.com/xuexb/github-bot/) 是用来演示 github-bot 的基本功能,因为具体需要实现的功能,可能因项目而不同,如果你需要她,你可以 fork 并相应的添加、删除功能。以下功能是一些常用的 show case 。 12 | 13 | ## 功能 - Feature 14 | 15 | ### Issue 16 | 17 | - [x] 没有使用 [创建 issue](https://xuexb.github.io/github-bot/create-issue.html) 页面提交的 issue 将直接被关闭 - [示例](https://github.com/xuexb/github-bot/issues/38#issuecomment-341050970) 18 | - [x] 根据 [创建 issue](https://xuexb.github.io/github-bot/create-issue.html) 页面提交的 issue 类型自动打上对应 label - [示例](https://github.com/xuexb/github-bot/issues/32#event-1317962655) 19 | - [x] 当 issue 标记 label 为 `need demo` 时,自动回复,需要相关demo - [示例](https://github.com/xuexb/github-bot/issues/14#issuecomment-336701988) 20 | - [x] issue 自动 assign 给指定人员,需要配置 `package.json` 中 `config.github-bot.labelToAuthor` 映射 - [示例](https://github.com/xuexb/github-bot/issues/32#event-1317962669) 21 | 22 | ### Pull Request 23 | 24 | - [x] 发 PR 时根据打的 label 自动添加指定的 reviewer ,需要配置 `package.json` 中 `config.github-bot.labelToAuthor` 映射 - [示例](https://github.com/xuexb/github-bot/pull/33#event-1320253347) 25 | - [x] 发 PR 时标题不规范时提醒修改,需要配置 `package.json` 中 `config.validate-commit-msg.type` 功能关键字,标题必须以 `功能关键字:` 开头 - [示例](https://github.com/xuexb/github-bot/pull/33#issuecomment-340650462) 26 | - [x] 发 PR 时自动根据标题的 [PR 标题规则](https://github.com/xuexb/github-bot#commit-log-和-pr-标题规则) 前缀生成对应的 label , `feat->enhancement, fix->bug` - [示例](https://github.com/xuexb/github-bot/pull/33#event-1320253315) 27 | 28 | ### Release 29 | 30 | - [x] 当往远程第一次推送新版本号时,自动列出最新版本距离上一版本的 commit log 并发布 release notes ,由于需要使用两个 tag 去对比,所以项目的第一个 tag 就不处理 - [示例](https://github.com/xuexb/github-bot/releases) 31 | 32 | ## 规则 - Rules 33 | 34 | ### issue 规则 35 | 36 | 必须使用 [创建 issue](https://xuexb.github.io/github-bot/create-issue.html) 页面来提交 issue ,否则将直接被关闭 37 | 38 | ### labels 规则 39 | 40 | - invalid - 未定义, 内容 不规范 41 | - need demo - 需要提供预览链接 42 | - need update - 需要更新修复问题 43 | - bug - bug 44 | - duplicate - 重复 45 | - enhancement - 新功能 46 | - question - 提问 47 | - wontfix - 不修复的问题 48 | 49 | ### commit log 和 PR 标题规则 50 | 51 | 所有标题必须以 `功能关键字:` 开头 52 | 53 | > 参考: 54 | 55 | 功能关键字介绍如下: 56 | 57 | - feat - 新功能(feature) 58 | - fix - 修补bug 59 | - docs - 文档(documentation) 60 | - style - 格式(不影响代码运行的变动) 61 | - test - 增加测试 62 | - chore - 构建过程或辅助工具的变动 63 | - revert - 撤销 64 | - close - 关闭 issue 65 | - release - 发布版本 66 | 67 | 示例: 68 | 69 | ``` 70 | close: #1, #3 71 | feat: 添加xx功能 72 | docs: update install info 73 | ``` 74 | 75 | ## 如何使用 76 | 77 | ### 1. 创建 access tokens 78 | 79 | (_需要在 .env 里配置_) 80 | 81 | ### 2. 创建 webhook 82 | 83 | https://github.com/用户名/项目名/settings/hooks/new 84 | 85 | - Payload URL: www.example.com:8000 86 | - Content type: application/json 87 | - trigger: Send me everything. 88 | - Secret: xxx (_需要在 .env 里配置_) 89 | 90 | ### 3. 开发运行 91 | 92 | ```bash 93 | npm install 94 | cp env .env 95 | vim .env 96 | npm start 97 | ``` 98 | 99 | ### 4. 部署 100 | 101 | 本项目使用 [pm2](https://github.com/Unitech/pm2) 进行服务管理,发布前请先全局安装 [pm2](https://github.com/Unitech/pm2) 102 | 103 | ```bash 104 | npm install pm2 -g 105 | npm run deploy 106 | ``` 107 | 108 | 后台启动该服务后,可以通过 `pm2 ls` 来查看服务名称为 `github-bot` 的运行状态。具体 [pm2](https://github.com/Unitech/pm2) 使用,请访问:https://github.com/Unitech/pm2 109 | 110 | ### 5. 日志系统说明 111 | 112 | 本系统 `logger` 服务基于 [log4js](https://github.com/log4js-node/log4js-node)。 113 | 在根目录的 `.env` 文件中有个参数 `LOG_TYPE` 默认为 `console`,参数值说明: 114 | 115 | ``` 116 | console - 通过 console 输出log。 117 | file - 将所有相关log输出到更根目录的 `log` 文件夹中。 118 | ``` 119 | 120 | ## contributors 121 | 122 | > [用户贡献指南](.github/CONTRIBUTING.md) 123 | 124 | - [@yugasun](https://github.com/yugasun/) 125 | - [@ddhhz](https://github.com/ddhhz) 126 | - [@xuexb](https://github.com/xuexb/) 127 | 128 | ## Liscense 129 | 130 | MIT 131 | -------------------------------------------------------------------------------- /create-issue.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 创建 issue - github-bot 6 | 7 | 8 | 109 | 110 | 111 |

创建 issue - github-bot

112 |

113 | 接受任何建议和意见,请认真填写下面表单,感谢您的反馈! 114 |

115 |
116 |
    117 |
  • 118 | 119 | 125 |
  • 126 |
  • 127 | 128 | 129 |
  • 130 |
  • 131 | 132 | 133 | 使用 node -v 可查看版本号。 134 |
  • 135 |
  • 136 | 137 | 138 |
  • 139 |
  • 140 | 发布是指生成指定内容到 Github 的 issue 页面,然后手动提交。 141 |
  • 142 |
143 |
144 | 145 | 146 | 147 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /env: -------------------------------------------------------------------------------- 1 | # rename this file to .env 2 | 3 | # Bot's personal access tokens, get from https://github.com/settings/tokens 4 | GITHUB_TOKEN=token 5 | 6 | # Webhook secret token, see https://developer.github.com/webhooks/securing/ 7 | GITHUB_SECRET_TOKEN=secret 8 | 9 | # Logger type: default is console, if you want write log to file, set 'file' 10 | LOG_TYPE=console 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-bot", 3 | "description": "Github bot", 4 | "version": "0.0.1", 5 | "main": "src/app.js", 6 | "scripts": { 7 | "start": "NODE_ENV=development node src/app", 8 | "lint": "eslint src/**/*.js test/**/*.js --quiet", 9 | "deploy": "pm2 start src/app.js --name=github-bot", 10 | "precommit": "npm run lint", 11 | "commitmsg": "validate-commit-msg", 12 | "test:watch": "npm run test -- --watch", 13 | "test:cov": "istanbul cover node_modules/mocha/bin/_mocha -- -t 5000 --recursive -R spec test/", 14 | "test": "mocha --reporter spec --timeout 5000 --recursive test/" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/xuexb/github-bot.git" 19 | }, 20 | "author": "xuexb ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/xuexb/github-bot/issues" 24 | }, 25 | "homepage": "https://github.com/xuexb/github-bot#readme", 26 | "dependencies": { 27 | "cryptiles": "3.1.2", 28 | "crypto": "^1.0.1", 29 | "dotenv": "^4.0.0", 30 | "github": "^11.0.0", 31 | "koa": "^2.3.0", 32 | "koa-bodyparser": "^4.2.0", 33 | "log4js": "^2.3.10", 34 | "require-dir": "^0.3.2", 35 | "string-template": "^1.0.0" 36 | }, 37 | "engines": { 38 | "node": ">= 7.8.0" 39 | }, 40 | "config": { 41 | "validate-commit-msg": { 42 | "types": [ 43 | "feat", 44 | "fix", 45 | "docs", 46 | "style", 47 | "refactor", 48 | "test", 49 | "chore", 50 | "revert", 51 | "release", 52 | "close" 53 | ] 54 | }, 55 | "github-bot": { 56 | "labelToAuthor": { 57 | "bug": "xuexb", 58 | "enhancement": "xuexb", 59 | "question": "xuexb" 60 | } 61 | } 62 | }, 63 | "devDependencies": { 64 | "chai": "^4.1.2", 65 | "chai-as-promised": "^7.1.1", 66 | "eslint": "^4.9.0", 67 | "eslint-config-standard": "^10.2.1", 68 | "eslint-friendly-formatter": "^3.0.0", 69 | "eslint-plugin-import": "^2.7.0", 70 | "eslint-plugin-node": "^5.2.0", 71 | "eslint-plugin-promise": "^3.6.0", 72 | "eslint-plugin-standard": "^3.0.1", 73 | "husky": "^0.14.3", 74 | "istanbul": ">=1.0.0-alpha.2", 75 | "mocha": "^4.0.1", 76 | "mock-require": "^2.0.2", 77 | "sinon": "^4.0.2", 78 | "sinon-chai": "^2.14.0", 79 | "validate-commit-msg": "^2.14.0" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file github-bot 入口文件 3 | * @author xuexb 4 | */ 5 | 6 | require('dotenv').config() 7 | 8 | const EventEmitter = require('events') 9 | const Koa = require('koa') 10 | const bodyParser = require('koa-bodyparser') 11 | const requireDir = require('require-dir') 12 | const { verifySignature } = require('./utils') 13 | const issueActions = requireDir('./modules/issues') 14 | const pullRequestActions = requireDir('./modules/pull_request') 15 | const releasesActions = requireDir('./modules/releases') 16 | const app = new Koa() 17 | const githubEvent = new EventEmitter() 18 | const { appLog, accessLog } = require('./logger') 19 | 20 | app.use(bodyParser()) 21 | 22 | app.use(ctx => { 23 | let eventName = ctx.request.headers['x-github-event'] 24 | if (eventName && verifySignature(ctx.request)) { 25 | const payload = ctx.request.body 26 | const action = payload.action || payload.ref_type 27 | 28 | if (action) { 29 | eventName += `_${action}` 30 | } 31 | 32 | accessLog.info(`receive event: ${eventName}`) 33 | 34 | githubEvent.emit(eventName, { 35 | repo: payload.repository.name, 36 | payload 37 | }) 38 | 39 | ctx.body = 'Ok.' 40 | } else { 41 | ctx.body = 'Go away.' 42 | } 43 | }) 44 | 45 | const actions = Object.assign({}, issueActions, pullRequestActions, releasesActions) 46 | Object.keys(actions).forEach((key) => { 47 | actions[key](githubEvent.on.bind(githubEvent)) 48 | appLog.info(`bind ${key} success!`) 49 | }) 50 | 51 | const port = 8000 52 | app.listen(port) 53 | appLog.info('Listening on http://0.0.0.0:', port) 54 | -------------------------------------------------------------------------------- /src/github.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file github 操作库 3 | * @author xuexb 4 | */ 5 | 6 | /* eslint-disable camelcase */ 7 | const GitHub = require('github') 8 | const { toArray } = require('./utils') 9 | const { appLog } = require('./logger') 10 | 11 | const github = new GitHub({ 12 | debug: process.env.NODE_ENV === 'development' 13 | }) 14 | 15 | github.authenticate({ 16 | type: 'token', 17 | token: process.env.GITHUB_TOKEN 18 | }) 19 | 20 | module.exports = { 21 | /** 22 | * issue 是否包含某 label 23 | * 24 | * @param {Object} payload data 25 | * @param {string} body 评论内容 26 | * @return {boolean} 27 | */ 28 | async issueHasLabel (payload, label) { 29 | const owner = payload.repository.owner.login 30 | const repo = payload.repository.name 31 | const number = payload.issue.number 32 | 33 | try { 34 | const res = await github.issues.getIssueLabels({ 35 | owner, 36 | repo, 37 | number 38 | }) 39 | return res.data.map(v => v.name).indexOf(label) > -1 40 | } catch (e) { 41 | appLog.error(new Error(e)) 42 | return false 43 | } 44 | }, 45 | 46 | /** 47 | * PR 是否包含某 label 48 | * 49 | * @param {Object} payload data 50 | * @param {string} body 评论内容 51 | * @return {boolean} 52 | */ 53 | async pullRequestHasLabel (payload, label) { 54 | const owner = payload.repository.owner.login 55 | const repo = payload.repository.name 56 | const number = payload.pull_request.number 57 | 58 | try { 59 | const res = await github.issues.getIssueLabels({ 60 | owner, 61 | repo, 62 | number 63 | }) 64 | return res.data.map(v => v.name).indexOf(label) > -1 65 | } catch (e) { 66 | appLog.error(new Error(e)) 67 | return false 68 | } 69 | }, 70 | 71 | /** 72 | * 评论 issue 73 | * 74 | * @param {Object} payload data 75 | * @param {string} body 评论内容 76 | * @return {boolean} 是否成功 77 | */ 78 | async commentIssue (payload, body) { 79 | const owner = payload.repository.owner.login 80 | const repo = payload.repository.name 81 | const number = payload.issue.number 82 | 83 | try { 84 | await github.issues.createComment({ 85 | owner, 86 | repo, 87 | number, 88 | body 89 | }) 90 | return true 91 | } catch (e) { 92 | appLog.error(new Error(e)) 93 | return false 94 | } 95 | }, 96 | 97 | /** 98 | * 评论 PR 99 | * 100 | * @param {Object} payload data 101 | * @param {string} body 评论内容 102 | * @return {boolean} 是否成功 103 | */ 104 | async commentPullRequest (payload, body) { 105 | const owner = payload.repository.owner.login 106 | const repo = payload.repository.name 107 | const number = payload.pull_request.number 108 | 109 | try { 110 | await github.issues.createComment({ 111 | owner, 112 | repo, 113 | number, 114 | body 115 | }) 116 | return true 117 | } catch (e) { 118 | appLog.error(new Error(e)) 119 | return false 120 | } 121 | }, 122 | 123 | /** 124 | * 关闭 issue 125 | * 126 | * @param {Object} payload data 127 | * @return {boolean} 是否成功 128 | */ 129 | async closeIssue (payload) { 130 | const owner = payload.repository.owner.login 131 | const repo = payload.repository.name 132 | const number = payload.issue.number 133 | 134 | try { 135 | await github.issues.edit({ 136 | owner, 137 | repo, 138 | number, 139 | state: 'closed' 140 | }) 141 | return true 142 | } catch (e) { 143 | appLog.error(new Error(e)) 144 | return false 145 | } 146 | }, 147 | 148 | /** 149 | * 分派作者到 issues 150 | * 151 | * @param {Object} payload data 152 | * @param {string | Array} assign 用户id 153 | * @return {boolean} 是否成功 154 | */ 155 | async addAssigneesToIssue (payload, assign) { 156 | const owner = payload.repository.owner.login 157 | const repo = payload.repository.name 158 | const number = payload.issue.number 159 | 160 | try { 161 | await github.issues.edit({ 162 | owner, 163 | repo, 164 | number, 165 | assignees: toArray(assign) 166 | }) 167 | return true 168 | } catch (e) { 169 | appLog.error(new Error(e)) 170 | return false 171 | } 172 | }, 173 | 174 | /** 175 | * 添加标签到 issue 176 | * 177 | * @param {Object} payload data 178 | * @param {string | Array} labels 标签 179 | * @return {boolean} 是否成功 180 | */ 181 | async addLabelsToIssue (payload, labels) { 182 | const owner = payload.repository.owner.login 183 | const repo = payload.repository.name 184 | const number = payload.issue.number 185 | 186 | try { 187 | await github.issues.addLabels({ 188 | owner, 189 | repo, 190 | number, 191 | labels: toArray(labels) 192 | }) 193 | return true 194 | } catch (e) { 195 | appLog.error(new Error(e)) 196 | return false 197 | } 198 | }, 199 | 200 | /** 201 | * 添加标签到 PR 202 | * 203 | * @param {Object} payload data 204 | * @param {string | Array} labels 标签 205 | * @return {boolean} 是否成功 206 | */ 207 | async addLabelsToPullRequest (payload, labels) { 208 | const owner = payload.repository.owner.login 209 | const repo = payload.repository.name 210 | const number = payload.pull_request.number 211 | 212 | try { 213 | await github.issues.addLabels({ 214 | owner, 215 | repo, 216 | number, 217 | labels: toArray(labels) 218 | }) 219 | return true 220 | } catch (e) { 221 | appLog.error(new Error(e)) 222 | return false 223 | } 224 | }, 225 | 226 | /** 227 | * 删除 PR 标签 228 | * 229 | * @param {Object} payload data 230 | * @param {string} name 标签名 231 | * @return {boolean} 是否成功 232 | */ 233 | async removeLabelsToPullRequest (payload, name) { 234 | const owner = payload.repository.owner.login 235 | const repo = payload.repository.name 236 | const number = payload.pull_request.number 237 | 238 | try { 239 | await github.issues.removeLabel({ 240 | owner, 241 | repo, 242 | number, 243 | name 244 | }) 245 | return true 246 | } catch (e) { 247 | appLog.error(new Error(e)) 248 | return false 249 | } 250 | }, 251 | 252 | /** 253 | * 删除 issue 标签 254 | * 255 | * @param {Object} payload data 256 | * @param {string} name 标签名 257 | * @return {boolean} 是否成功 258 | */ 259 | async removeLabelsToIssue (payload, name) { 260 | const owner = payload.repository.owner.login 261 | const repo = payload.repository.name 262 | const number = payload.issue.number 263 | try { 264 | await github.issues.removeLabel({ 265 | owner, 266 | repo, 267 | number, 268 | name 269 | }) 270 | return true 271 | } catch (e) { 272 | appLog.error(new Error(e)) 273 | return false 274 | } 275 | }, 276 | 277 | /** 278 | * 创建发布 279 | * 280 | * @param {Object} payload data 281 | * @param {string} options.tag_name tag名 282 | * @param {string} options.target_commitish tag hash 283 | * @param {string} options.name 标题 284 | * @param {string} options.body 内容 285 | * @param {boolean} options.draft 是否为草稿 286 | * @param {boolean} options.prerelease 是否预发布 287 | * @return {boolean} 是否成功 288 | */ 289 | async createRelease (payload, { tag_name, target_commitish, name, body, draft, prerelease } = {}) { 290 | const owner = payload.repository.owner.login 291 | const repo = payload.repository.name 292 | try { 293 | await github.repos.createRelease({ 294 | owner, 295 | repo, 296 | tag_name, 297 | target_commitish, 298 | name, 299 | body, 300 | draft, 301 | prerelease 302 | }) 303 | return true 304 | } catch (e) { 305 | appLog.error(new Error(e)) 306 | return false 307 | } 308 | }, 309 | 310 | /** 311 | * 根据tag获取发布信息 312 | * 313 | * @param {Object} payload data 314 | * @param {string} options.tag_name tag名 315 | * 316 | * @return {Object | null} 317 | */ 318 | async getReleaseByTag (payload, { tag_name } = {}) { 319 | const owner = payload.repository.owner.login 320 | const repo = payload.repository.name 321 | try { 322 | const res = await github.repos.getReleaseByTag({ 323 | owner, 324 | repo, 325 | tag: tag_name 326 | }) 327 | return res.data 328 | } catch (e) { 329 | appLog.error(new Error(e)) 330 | return null 331 | } 332 | }, 333 | 334 | /** 335 | * 创建 review 请求 336 | * 337 | * @param {Object} payload data 338 | * @param {Array | string} options.reviewers reviewer 339 | * @param {Array | string} options.team_reviewers team_reviewers 340 | * 341 | * @return {boolean} 是否成功 342 | */ 343 | async createReviewRequest (payload, { reviewers, team_reviewers } = {}) { 344 | const owner = payload.repository.owner.login 345 | const repo = payload.repository.name 346 | const number = payload.pull_request.number 347 | try { 348 | await github.pullRequests.createReviewRequest({ 349 | owner, 350 | repo, 351 | number, 352 | reviewers: toArray(reviewers), 353 | team_reviewers: toArray(team_reviewers) 354 | }) 355 | return true 356 | } catch (e) { 357 | appLog.error(new Error(e)) 358 | return false 359 | } 360 | }, 361 | 362 | /** 363 | * 获得 repo 所有的tag 364 | * 365 | * @param {any} payload data 366 | * @return {Array} 367 | */ 368 | async getTags (payload) { 369 | const owner = payload.repository.owner.login 370 | const repo = payload.repository.name 371 | try { 372 | const res = await github.repos.getTags({ 373 | owner, 374 | repo 375 | }) 376 | return res.data 377 | } catch (e) { 378 | appLog.error(new Error(e)) 379 | return [] 380 | } 381 | }, 382 | 383 | /** 384 | * 对比2个提交 385 | * 386 | * @param {Object} payload data 387 | * @param {string} options.base 基点 388 | * @param {string} options.head diff 389 | * @return {Array | null} 390 | */ 391 | async compareCommits (payload, { base, head } = {}) { 392 | const owner = payload.repository.owner.login 393 | const repo = payload.repository.name 394 | try { 395 | const res = await github.repos.compareCommits({ 396 | owner, 397 | repo, 398 | base, 399 | head 400 | }) 401 | return res.data 402 | } catch (e) { 403 | appLog.error(new Error(e)) 404 | return null 405 | } 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created Date: Wednesday, November 1st 2017, 4:03:55 pm 3 | * Author: yugasun 4 | * Email: yuga.sun.bj@gmail.com 5 | * ----- 6 | * Last Modified: 7 | * Modified By: 8 | * ----- 9 | * Copyright (c) 2017 yugasun 10 | */ 11 | 12 | const log4js = require('log4js') 13 | 14 | const fileConfig = { 15 | pm2: true, 16 | appenders: { 17 | app: { 18 | 'type': 'file', 19 | 'filename': 'log/app.log', 20 | 'maxLogSize': 10485760, 21 | 'numBackups': 3 22 | }, 23 | access: { 24 | 'type': 'dateFile', 25 | 'filename': 'log/access.log', 26 | 'pattern': '-yyyy-MM-dd', 27 | 'category': 'http' 28 | }, 29 | errorFile: { type: 'file', filename: 'log/errors.log' }, 30 | errors: { 31 | 'type': 'logLevelFilter', 32 | 'level': 'error', 33 | 'appender': 'errorFile' 34 | } 35 | }, 36 | categories: { 37 | default: { appenders: ['app', 'errors'], level: 'trace' }, 38 | http: { appenders: ['access'], level: 'info' } 39 | } 40 | } 41 | const consoleConfig = { 42 | pm2: true, 43 | appenders: { 44 | console: { type: 'console' } 45 | }, 46 | categories: { 47 | default: { appenders: ['console'], level: 'info' }, 48 | http: { appenders: ['console'], level: 'info' } 49 | } 50 | } 51 | 52 | const config = process.env.LOG_TYPE === 'file' ? fileConfig : consoleConfig 53 | 54 | log4js.configure(config) 55 | 56 | const appLog = log4js.getLogger('app') 57 | const accessLog = log4js.getLogger('http') 58 | 59 | module.exports = { 60 | appLog, 61 | accessLog 62 | } 63 | -------------------------------------------------------------------------------- /src/modules/issues/autoAssign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file issue 自动 `assign` 给指定人员 3 | * @author xuexb 4 | */ 5 | 6 | const { getPkgConfig } = require('../../utils') 7 | const { addAssigneesToIssue } = require('../../github') 8 | 9 | const config = getPkgConfig() 10 | const assignMap = config.labelToAuthor || {} 11 | 12 | function autoAssign (on) { 13 | on('issues_labeled', ({ payload, repo }) => { 14 | if (assignMap[payload.label.name]) { 15 | addAssigneesToIssue( 16 | payload, 17 | assignMap[payload.label.name] 18 | ) 19 | } 20 | }) 21 | } 22 | 23 | module.exports = autoAssign 24 | -------------------------------------------------------------------------------- /src/modules/issues/autoLabel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 自动根据创建的 issue 内标识创建对应 label 3 | * @author xuexb 4 | */ 5 | 6 | const { addLabelsToIssue } = require('../../github') 7 | 8 | function autoAssign (on) { 9 | on('issues_opened', ({ payload, repo }) => { 10 | const label = (payload.issue.body.match(//) || [])[1] 11 | if (label) { 12 | addLabelsToIssue(payload, label) 13 | } 14 | }) 15 | } 16 | 17 | module.exports = autoAssign 18 | -------------------------------------------------------------------------------- /src/modules/issues/replyInvalid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 不规范issue则自动关闭 3 | * @author xuexb 4 | */ 5 | 6 | const format = require('string-template') 7 | const { 8 | commentIssue, 9 | closeIssue, 10 | addLabelsToIssue 11 | } = require('../../github') 12 | 13 | const comment = [ 14 | 'hi @{user},非常感谢您的反馈,', 15 | '但是由于您没有使用 [创建 issue](https://xuexb.github.io/github-bot/create-issue.html) 页面提交, 将直接被关闭, 谢谢!' 16 | ].join('') 17 | 18 | function replyInvalid (on) { 19 | on('issues_opened', ({ payload }) => { 20 | const issue = payload.issue 21 | const opener = issue.user.login 22 | 23 | if (issue.body.indexOf('') === -1) { 24 | commentIssue( 25 | payload, 26 | format(comment, { 27 | user: opener 28 | }) 29 | ) 30 | 31 | closeIssue(payload) 32 | addLabelsToIssue(payload, 'invalid') 33 | } 34 | }) 35 | } 36 | 37 | module.exports = replyInvalid 38 | -------------------------------------------------------------------------------- /src/modules/issues/replyNeedDemo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 当有 need demo 标签时自动回复需要相关预览链接 3 | * @author xuexb 4 | */ 5 | 6 | const format = require('string-template') 7 | const { commentIssue } = require('../../github') 8 | 9 | const comment = 'hi @{user},请提供一个可预览的链接,如: ' 10 | 11 | function replyNeedDemo (on) { 12 | on('issues_labeled', ({ payload, repo }) => { 13 | if (payload.label.name === 'need demo') { 14 | commentIssue( 15 | payload, 16 | format(comment, { 17 | user: payload.issue.user.login 18 | }) 19 | ) 20 | } 21 | }) 22 | } 23 | 24 | module.exports = replyNeedDemo 25 | -------------------------------------------------------------------------------- /src/modules/pull_request/autoReviewRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file PR 自动根据 tag 去添加 reviewer 3 | * @author xuexb 4 | */ 5 | 6 | const { getPkgConfig } = require('../../utils') 7 | const { createReviewRequest } = require('../../github') 8 | 9 | const config = getPkgConfig() 10 | const assignMap = config.labelToAuthor || {} 11 | 12 | module.exports = on => { 13 | on('pull_request_labeled', ({ payload, repo }) => { 14 | if (assignMap[payload.label.name]) { 15 | createReviewRequest( 16 | payload, 17 | { 18 | reviewers: assignMap[payload.label.name] 19 | } 20 | ) 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/pull_request/replyInvalidTitle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file PR 提示标题正确性 3 | * @author xuexb 4 | */ 5 | 6 | const format = require('string-template') 7 | const { getPkgCommitPrefix } = require('../../utils') 8 | const { 9 | commentPullRequest, 10 | addLabelsToPullRequest, 11 | removeLabelsToPullRequest, 12 | pullRequestHasLabel 13 | } = require('../../github') 14 | 15 | const actions = getPkgCommitPrefix() 16 | const match = title => { 17 | return actions.some(action => title.indexOf(`${action}:`) === 0) 18 | } 19 | 20 | const commentSuccess = [ 21 | 'hi @{user},非常感谢您及时修正标题格式,祝您玩的开心!' 22 | ].join('') 23 | 24 | const commentError = [ 25 | 'hi @{user},非常感谢您的 PR ,', 26 | '但是您没有使用 [PR 标题规则](https://github.com/xuexb/github-bot#commit-log-和-pr-标题规则) 格式,', 27 | '请及时修改, 谢谢!' 28 | ].join('') 29 | 30 | module.exports = on => { 31 | if (actions.length) { 32 | on('pull_request_opened', ({ payload, repo }) => { 33 | if (!match(payload.pull_request.title)) { 34 | commentPullRequest( 35 | payload, 36 | format(commentError, { 37 | user: payload.pull_request.user.login 38 | }) 39 | ) 40 | 41 | addLabelsToPullRequest(payload, 'invalid') 42 | } 43 | }) 44 | 45 | on('pull_request_edited', async ({ payload, repo }) => { 46 | if (match(payload.pull_request.title) && await pullRequestHasLabel(payload, 'invalid')) { 47 | commentPullRequest( 48 | payload, 49 | format(commentSuccess, { 50 | user: payload.pull_request.user.login 51 | }) 52 | ) 53 | 54 | removeLabelsToPullRequest(payload, 'invalid') 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/modules/pull_request/titlePrefixToLabel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file PR 标题自动打标签 3 | * @author xuexb 4 | */ 5 | 6 | const { 7 | addLabelsToPullRequest, 8 | pullRequestHasLabel 9 | } = require('../../github') 10 | 11 | const getAction = title => { 12 | return (title.match(/^(\w+?):/) || [])[1] 13 | } 14 | 15 | const ACTION_TO_LABEL_MAP = { 16 | feat: 'enhancement', 17 | fix: 'bug', 18 | docs: 'document' 19 | } 20 | 21 | const handle = async ({ payload, repo }) => { 22 | const action = getAction(payload.pull_request.title) 23 | if (action && ACTION_TO_LABEL_MAP[action]) { 24 | const exist = await pullRequestHasLabel(payload, ACTION_TO_LABEL_MAP[action]) 25 | if (!exist) { 26 | addLabelsToPullRequest(payload, ACTION_TO_LABEL_MAP[action]) 27 | } 28 | } 29 | } 30 | 31 | module.exports = on => { 32 | on('pull_request_edited', handle) 33 | on('pull_request_opened', handle) 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/releases/autoReleaseNote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 根据 tag 自动 release 3 | * @author xuexb 4 | */ 5 | 6 | const { 7 | getTags, 8 | compareCommits, 9 | getReleaseByTag, 10 | createRelease 11 | } = require('../../github') 12 | 13 | const RELEASE_CHANGE_MAP = { 14 | document: 'docs', 15 | feature: 'feat', 16 | bugfix: 'fix', 17 | close: 'close' 18 | } 19 | 20 | module.exports = on => { 21 | on('create_tag', async ({ payload, repo }) => { 22 | const tag = await getReleaseByTag(payload, { 23 | tag_name: payload.ref 24 | }) 25 | // 如果该 tag 存在则直接返回 26 | if (tag !== null) { 27 | return 28 | } 29 | 30 | const tags = await getTags(payload) 31 | 32 | // 如果只有一个 tag 则没法对比,忽略 33 | if (tags.length < 2) { 34 | return 35 | } 36 | 37 | const head = tags[0].name 38 | const base = tags[1].name 39 | 40 | const commitsLog = await compareCommits(payload, { 41 | base, 42 | head 43 | }) 44 | 45 | const commits = commitsLog.commits 46 | const changes = Object.keys(RELEASE_CHANGE_MAP).map(title => { 47 | return { 48 | title, 49 | data: commits 50 | .filter((commit) => commit.commit.message.indexOf(`${RELEASE_CHANGE_MAP[title]}:`) === 0) 51 | .map((commit) => { 52 | let message = commit.commit.message 53 | // 处理 squash merge 的 commit message 54 | if (message.indexOf('\n') !== -1) { 55 | message = message.substr(0, message.indexOf('\n')) 56 | } 57 | return `- ${message}, by @${commit.author.login} <<${commit.commit.author.email}>>` 58 | }) 59 | } 60 | }).filter(v => v.data.length) 61 | 62 | const hashChanges = commits.map((commit) => { 63 | let message = commit.commit.message 64 | // 处理 squash merge 的 commit message 65 | if (message.indexOf('\n') !== -1) { 66 | message = message.substr(0, message.indexOf('\n')) 67 | } 68 | return `- [${commit.sha.substr(0, 7)}](${commit.html_url}) - ${message}, by @${commit.author.login} <<${commit.commit.author.email}>>` 69 | }) 70 | 71 | let body = [] 72 | 73 | if (changes.length) { 74 | body.push('## Notable changes\n') 75 | changes.forEach(v => { 76 | body.push(`- ${v.title}`) 77 | 78 | v.data.forEach(line => body.push(' ' + line)) 79 | }) 80 | } 81 | 82 | if (hashChanges.length) { 83 | body.push('\n## Commits\n') 84 | body = body.concat(hashChanges) 85 | } 86 | 87 | if (body.length) { 88 | createRelease(payload, { 89 | tag_name: payload.ref, 90 | name: `${payload.ref} @${payload.repository.owner.login}`, 91 | body: body.join('\n') 92 | }) 93 | } 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 工具集 3 | * @author xuexb 4 | */ 5 | const crypto = require('crypto') 6 | const { fixedTimeComparison } = require('cryptiles') 7 | 8 | const utils = { 9 | 10 | /** 11 | * 验证请求 12 | * 13 | * @param {Object} request req 14 | * 15 | * @return {boolean} 16 | */ 17 | verifySignature (request) { 18 | let signature = crypto.createHmac('sha1', process.env.GITHUB_SECRET_TOKEN) 19 | .update(request.rawBody) 20 | .digest('hex') 21 | signature = `sha1=${signature}` 22 | return fixedTimeComparison(signature, request.headers['x-hub-signature']) 23 | }, 24 | 25 | /** 26 | * 获取 package.json 里的 config.github-bot 27 | * 28 | * @return {Object} 29 | */ 30 | getPkgConfig () { 31 | const pkg = require('../package.json') 32 | const config = Object.assign({ 33 | 'github-bot': {} 34 | }, pkg.config) 35 | 36 | return config['github-bot'] 37 | }, 38 | 39 | /** 40 | * 获取 commit log 前缀白名单 41 | * 42 | * @return {Array} 43 | */ 44 | getPkgCommitPrefix () { 45 | const pkg = require('../package.json') 46 | const config = Object.assign({ 47 | 'validate-commit-msg': { 48 | 'types': [] 49 | } 50 | }, pkg.config) 51 | 52 | return config['validate-commit-msg'].types 53 | }, 54 | 55 | /** 56 | * 转化成 Array 57 | * 58 | * @param {string | Array} str 目标值 59 | * 60 | * @return {Array} 61 | */ 62 | toArray (str) { 63 | if (str) { 64 | return Array.isArray(str) ? str : [str] 65 | } 66 | 67 | return str 68 | } 69 | } 70 | 71 | module.exports = utils 72 | -------------------------------------------------------------------------------- /test/github.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file github.js test case 3 | * @author xuexb 4 | */ 5 | 6 | /* eslint-disable camelcase */ 7 | const mock = require('mock-require') 8 | mock.stopAll() 9 | const chai = require('chai') 10 | const expect = chai.expect 11 | const chaiAsPromised = require('chai-as-promised') 12 | const clean = require('./utils/clean') 13 | chai.use(chaiAsPromised) 14 | 15 | const payload = { 16 | repository: { 17 | owner: { 18 | login: 'xuexb' 19 | }, 20 | name: 'github-bot' 21 | }, 22 | pull_request: { 23 | number: 1 24 | }, 25 | issue: { 26 | number: 1 27 | } 28 | } 29 | 30 | const createClass = (constructor, prototype) => { 31 | function Class (...args) { 32 | if ('function' === typeof constructor) { 33 | constructor.apply(this, args) 34 | } 35 | } 36 | Object.assign(Class.prototype, { 37 | authenticate() {} 38 | }, prototype) 39 | 40 | return Class 41 | } 42 | 43 | const mockGithub = (...args) => { 44 | return mock('github', createClass(...args)) 45 | } 46 | 47 | describe('github.js', () => { 48 | beforeEach('clear node cache', () => { 49 | clean('src/github') 50 | }) 51 | 52 | describe('.issueHasLabel', () => { 53 | it('should be a method', () => { 54 | mockGithub(null, { 55 | issues: { 56 | getIssueLabels() { 57 | return Promise.resolve({ 58 | data: [] 59 | }) 60 | } 61 | } 62 | }) 63 | const github = require('../src/github') 64 | expect(github.issueHasLabel).to.be.a('function') 65 | }) 66 | 67 | describe('should return boolean', () => { 68 | it('true', () => { 69 | mockGithub(null, { 70 | issues: { 71 | getIssueLabels() { 72 | return Promise.resolve({ 73 | data: [ 74 | { 75 | name: 'nofond' 76 | } 77 | ] 78 | }) 79 | } 80 | } 81 | }) 82 | const github = require('../src/github') 83 | expect(github.issueHasLabel(payload, 'nofond')).to.eventually.be.true 84 | }) 85 | 86 | it('false', () => { 87 | mockGithub(null, { 88 | issues: { 89 | getIssueLabels() { 90 | return Promise.resolve({ 91 | data: [ 92 | { 93 | name: 'test' 94 | } 95 | ] 96 | }) 97 | } 98 | } 99 | }) 100 | const github = require('../src/github') 101 | expect(github.issueHasLabel(payload, 'nofond')).to.eventually.be.false 102 | }) 103 | 104 | it('error', () => { 105 | mock('../src/logger', { 106 | appLog: { 107 | error(err) { 108 | expect(err).to.not.be.undefined 109 | } 110 | } 111 | }) 112 | mockGithub(null) 113 | const github = require('../src/github') 114 | expect(github.issueHasLabel(payload, 'nofond')).to.eventually.be.false 115 | }) 116 | }) 117 | 118 | it('check param', () => { 119 | mockGithub(null, { 120 | issues: { 121 | getIssueLabels({owner, repo, number}) { 122 | expect(owner).to.equal('xuexb') 123 | expect(repo).to.equal('github-bot') 124 | expect(number).to.equal(1) 125 | return Promise.resolve({ 126 | data: [] 127 | }) 128 | } 129 | } 130 | }) 131 | const github = require('../src/github') 132 | return github.issueHasLabel(payload) 133 | }) 134 | 135 | }) 136 | 137 | describe('.pullRequestHasLabel', () => { 138 | it('should be a method', () => { 139 | mockGithub(null, { 140 | issues: { 141 | getIssueLabels() { 142 | return Promise.resolve({ 143 | data: [] 144 | }) 145 | } 146 | } 147 | }) 148 | const github = require('../src/github') 149 | expect(github.pullRequestHasLabel).to.be.a('function') 150 | }) 151 | 152 | describe('should return boolean', () => { 153 | it('true', () => { 154 | mockGithub(null, { 155 | issues: { 156 | getIssueLabels() { 157 | return Promise.resolve({ 158 | data: [ 159 | { 160 | name: 'nofond' 161 | } 162 | ] 163 | }) 164 | } 165 | } 166 | }) 167 | const github = require('../src/github') 168 | expect(github.pullRequestHasLabel(payload, 'nofond')).to.eventually.be.true 169 | }) 170 | 171 | it('false', () => { 172 | mockGithub(null, { 173 | issues: { 174 | getIssueLabels() { 175 | return Promise.resolve({ 176 | data: [ 177 | { 178 | name: 'test' 179 | } 180 | ] 181 | }) 182 | } 183 | } 184 | }) 185 | const github = require('../src/github') 186 | expect(github.pullRequestHasLabel(payload, 'nofond')).to.eventually.be.false 187 | }) 188 | 189 | it('error', () => { 190 | mock('../src/logger', { 191 | appLog: { 192 | error(err) { 193 | expect(err).to.not.be.undefined 194 | } 195 | } 196 | }) 197 | mockGithub(null) 198 | const github = require('../src/github') 199 | expect(github.pullRequestHasLabel(payload)).to.eventually.be.false 200 | }) 201 | }) 202 | 203 | it('check param', () => { 204 | mockGithub(null, { 205 | issues: { 206 | getIssueLabels({owner, repo, number}) { 207 | expect(owner).to.equal('xuexb') 208 | expect(repo).to.equal('github-bot') 209 | expect(number).to.equal(1) 210 | return Promise.resolve({ 211 | data: [] 212 | }) 213 | } 214 | } 215 | }) 216 | const github = require('../src/github') 217 | return github.pullRequestHasLabel(payload) 218 | }) 219 | }) 220 | 221 | describe('.commentIssue', () => { 222 | it('should be a method', () => { 223 | mockGithub(null, { 224 | issues: { 225 | createComment() { 226 | return Promise.resolve() 227 | } 228 | } 229 | }) 230 | const github = require('../src/github') 231 | expect(github.commentIssue).to.be.a('function') 232 | }) 233 | 234 | describe('should return boolean', () => { 235 | it('true', () => { 236 | mockGithub(null, { 237 | issues: { 238 | createComment() { 239 | return Promise.resolve() 240 | } 241 | } 242 | }) 243 | const github = require('../src/github') 244 | expect(github.commentIssue(payload, 'message')).to.eventually.be.true 245 | }) 246 | 247 | it('false', () => { 248 | mockGithub(null, { 249 | issues: { 250 | createComment() { 251 | throw new TypeError('error') 252 | } 253 | } 254 | }) 255 | const github = require('../src/github') 256 | expect(github.commentIssue(payload)).to.eventually.be.false 257 | }) 258 | 259 | it('error', () => { 260 | mock('../src/logger', { 261 | appLog: { 262 | error(err) { 263 | expect(err).to.not.be.undefined 264 | } 265 | } 266 | }) 267 | mockGithub(null) 268 | const github = require('../src/github') 269 | expect(github.commentIssue(payload)).to.eventually.be.false 270 | }) 271 | }) 272 | 273 | it('check param', () => { 274 | mockGithub(null, { 275 | issues: { 276 | createComment({owner, repo, number}) { 277 | expect(owner).to.equal('xuexb') 278 | expect(repo).to.equal('github-bot') 279 | expect(number).to.equal(1) 280 | expect(body).to.equal('message') 281 | return Promise.resolve() 282 | } 283 | } 284 | }) 285 | const github = require('../src/github') 286 | return github.commentIssue(payload, 'message') 287 | }) 288 | }) 289 | 290 | describe('.commentPullRequest', () => { 291 | it('should be a method', () => { 292 | mockGithub(null, { 293 | issues: { 294 | createComment() { 295 | return Promise.resolve() 296 | } 297 | } 298 | }) 299 | const github = require('../src/github') 300 | expect(github.commentPullRequest).to.be.a('function') 301 | }) 302 | 303 | describe('should return boolean', () => { 304 | it('true', () => { 305 | mockGithub(null, { 306 | issues: { 307 | createComment() { 308 | return Promise.resolve() 309 | } 310 | } 311 | }) 312 | const github = require('../src/github') 313 | expect(github.commentPullRequest(payload, 'message')).to.eventually.be.true 314 | }) 315 | 316 | it('false', () => { 317 | mockGithub(null, { 318 | issues: { 319 | createComment() { 320 | throw new TypeError('error') 321 | } 322 | } 323 | }) 324 | const github = require('../src/github') 325 | expect(github.commentPullRequest(payload)).to.eventually.be.false 326 | }) 327 | 328 | it('error', () => { 329 | mock('../src/logger', { 330 | appLog: { 331 | error(err) { 332 | expect(err).to.not.be.undefined 333 | } 334 | } 335 | }) 336 | mockGithub(null) 337 | const github = require('../src/github') 338 | expect(github.commentPullRequest(payload)).to.eventually.be.false 339 | }) 340 | }) 341 | 342 | it('check param', () => { 343 | mockGithub(null, { 344 | issues: { 345 | createComment({owner, repo, number}) { 346 | expect(owner).to.equal('xuexb') 347 | expect(repo).to.equal('github-bot') 348 | expect(number).to.equal(1) 349 | expect(body).to.equal('message') 350 | return Promise.resolve() 351 | } 352 | } 353 | }) 354 | const github = require('../src/github') 355 | return github.commentPullRequest(payload, 'message') 356 | }) 357 | }) 358 | 359 | describe('.closeIssue', () => { 360 | it('should be a method', () => { 361 | mockGithub(null, { 362 | issues: { 363 | edit() { 364 | return Promise.resolve() 365 | } 366 | } 367 | }) 368 | const github = require('../src/github') 369 | expect(github.closeIssue).to.be.a('function') 370 | }) 371 | 372 | describe('should return boolean', () => { 373 | it('true', () => { 374 | mockGithub(null, { 375 | issues: { 376 | edit() { 377 | return Promise.resolve() 378 | } 379 | } 380 | }) 381 | const github = require('../src/github') 382 | expect(github.closeIssue(payload)).to.eventually.be.true 383 | }) 384 | 385 | it('false', () => { 386 | mockGithub(null, { 387 | issues: { 388 | edit() { 389 | throw new TypeError('error') 390 | } 391 | } 392 | }) 393 | const github = require('../src/github') 394 | expect(github.closeIssue(payload)).to.eventually.be.false 395 | }) 396 | 397 | it('error', () => { 398 | mock('../src/logger', { 399 | appLog: { 400 | error(err) { 401 | expect(err).to.not.be.undefined 402 | } 403 | } 404 | }) 405 | mockGithub(null) 406 | const github = require('../src/github') 407 | expect(github.closeIssue(payload)).to.eventually.be.false 408 | }) 409 | }) 410 | 411 | it('check param', () => { 412 | mockGithub(null, { 413 | issues: { 414 | edit({owner, repo, number, state}) { 415 | expect(owner).to.equal('xuexb') 416 | expect(repo).to.equal('github-bot') 417 | expect(number).to.equal(1) 418 | expect(state).to.equal('closed') 419 | return Promise.resolve() 420 | } 421 | } 422 | }) 423 | const github = require('../src/github') 424 | return github.closeIssue(payload) 425 | }) 426 | }) 427 | 428 | describe('.addAssigneesToIssue', () => { 429 | it('should be a method', () => { 430 | mockGithub(null, { 431 | issues: { 432 | edit() { 433 | return Promise.resolve() 434 | } 435 | } 436 | }) 437 | const github = require('../src/github') 438 | expect(github.addAssigneesToIssue).to.be.a('function') 439 | }) 440 | 441 | describe('should return boolean', () => { 442 | it('true', () => { 443 | mockGithub(null, { 444 | issues: { 445 | edit() { 446 | return Promise.resolve() 447 | } 448 | } 449 | }) 450 | const github = require('../src/github') 451 | expect(github.addAssigneesToIssue(payload)).to.eventually.be.true 452 | }) 453 | 454 | it('false', () => { 455 | mockGithub(null, { 456 | issues: { 457 | edit() { 458 | throw new TypeError('error') 459 | } 460 | } 461 | }) 462 | const github = require('../src/github') 463 | expect(github.addAssigneesToIssue(payload)).to.eventually.be.false 464 | }) 465 | 466 | it('error', () => { 467 | mock('../src/logger', { 468 | appLog: { 469 | error(err) { 470 | expect(err).to.not.be.undefined 471 | } 472 | } 473 | }) 474 | mockGithub(null, null) 475 | const github = require('../src/github') 476 | expect(github.addAssigneesToIssue(payload)).to.eventually.be.false 477 | }) 478 | }) 479 | 480 | it('check param', () => { 481 | mockGithub(null, { 482 | issues: { 483 | edit({owner, repo, number, state}) { 484 | expect(owner).to.equal('xuexb') 485 | expect(repo).to.equal('github-bot') 486 | expect(number).to.equal(1) 487 | expect(assignees).to.deep.equal(['ok']) 488 | return Promise.resolve() 489 | } 490 | } 491 | }) 492 | const github = require('../src/github') 493 | return github.addAssigneesToIssue(payload, 'ok') 494 | }) 495 | }) 496 | 497 | describe('.addLabelsToIssue', () => { 498 | it('should be a method', () => { 499 | mockGithub(null, { 500 | issues: { 501 | addLabels() { 502 | return Promise.resolve() 503 | } 504 | } 505 | }) 506 | const github = require('../src/github') 507 | expect(github.addLabelsToIssue).to.be.a('function') 508 | }) 509 | 510 | describe('should return boolean', () => { 511 | it('true', () => { 512 | mockGithub(null, { 513 | issues: { 514 | addLabels() { 515 | return Promise.resolve() 516 | } 517 | } 518 | }) 519 | const github = require('../src/github') 520 | expect(github.addLabelsToIssue(payload, 'label')).to.eventually.be.true 521 | }) 522 | 523 | it('false', () => { 524 | mockGithub(null, { 525 | issues: { 526 | addLabels() { 527 | throw new TypeError('error') 528 | } 529 | } 530 | }) 531 | const github = require('../src/github') 532 | expect(github.addLabelsToIssue(payload)).to.eventually.be.false 533 | }) 534 | 535 | it('error', () => { 536 | mock('../src/logger', { 537 | appLog: { 538 | error(err) { 539 | expect(err).to.not.be.undefined 540 | } 541 | } 542 | }) 543 | mockGithub(null) 544 | const github = require('../src/github') 545 | expect(github.addLabelsToIssue(payload)).to.eventually.be.false 546 | }) 547 | }) 548 | 549 | it('check param', () => { 550 | mockGithub(null, { 551 | issues: { 552 | addLabels({owner, repo, number, state}) { 553 | expect(owner).to.equal('xuexb') 554 | expect(repo).to.equal('github-bot') 555 | expect(number).to.equal(1) 556 | expect(assignees).to.deep.equal(['ok']) 557 | return Promise.resolve() 558 | } 559 | } 560 | }) 561 | const github = require('../src/github') 562 | return github.addLabelsToIssue(payload, 'ok') 563 | }) 564 | }) 565 | 566 | describe('.addLabelsToPullRequest', () => { 567 | it('should be a method', () => { 568 | mockGithub(null, { 569 | issues: { 570 | addLabels() { 571 | return Promise.resolve() 572 | } 573 | } 574 | }) 575 | const github = require('../src/github') 576 | expect(github.addLabelsToPullRequest).to.be.a('function') 577 | }) 578 | 579 | describe('should return boolean', () => { 580 | it('true', () => { 581 | mockGithub(null, { 582 | issues: { 583 | addLabels() { 584 | return Promise.resolve() 585 | } 586 | } 587 | }) 588 | const github = require('../src/github') 589 | expect(github.addLabelsToPullRequest(payload, 'label')).to.eventually.be.true 590 | }) 591 | 592 | it('false', () => { 593 | mockGithub(null, { 594 | issues: { 595 | addLabels() { 596 | throw new TypeError('error') 597 | } 598 | } 599 | }) 600 | const github = require('../src/github') 601 | expect(github.addLabelsToPullRequest(payload)).to.eventually.be.false 602 | }) 603 | 604 | it('error', () => { 605 | mock('../src/logger', { 606 | appLog: { 607 | error(err) { 608 | expect(err).to.not.be.undefined 609 | } 610 | } 611 | }) 612 | mockGithub(null) 613 | const github = require('../src/github') 614 | expect(github.addLabelsToPullRequest(payload)).to.eventually.be.false 615 | }) 616 | }) 617 | 618 | it('check param', () => { 619 | mockGithub(null, { 620 | issues: { 621 | addLabels({owner, repo, number, state}) { 622 | expect(owner).to.equal('xuexb') 623 | expect(repo).to.equal('github-bot') 624 | expect(number).to.equal(1) 625 | expect(assignees).to.deep.equal(['ok']) 626 | return Promise.resolve() 627 | } 628 | } 629 | }) 630 | const github = require('../src/github') 631 | return github.addLabelsToPullRequest(payload, 'ok') 632 | }) 633 | }) 634 | 635 | describe('.removeLabelsToPullRequest', () => { 636 | it('should be a method', () => { 637 | mockGithub(null, { 638 | issues: { 639 | removeLabel() { 640 | return Promise.resolve() 641 | } 642 | } 643 | }) 644 | const github = require('../src/github') 645 | expect(github.removeLabelsToPullRequest).to.be.a('function') 646 | }) 647 | 648 | describe('should return boolean', () => { 649 | it('true', () => { 650 | mockGithub(null, { 651 | issues: { 652 | removeLabel() { 653 | return Promise.resolve() 654 | } 655 | } 656 | }) 657 | const github = require('../src/github') 658 | expect(github.removeLabelsToPullRequest(payload, 'label')).to.eventually.be.true 659 | }) 660 | 661 | it('false', () => { 662 | mockGithub(null, { 663 | issues: { 664 | removeLabel() { 665 | throw new TypeError('error') 666 | } 667 | } 668 | }) 669 | const github = require('../src/github') 670 | expect(github.removeLabelsToPullRequest(payload)).to.eventually.be.false 671 | }) 672 | 673 | it('error', () => { 674 | mock('../src/logger', { 675 | appLog: { 676 | error(err) { 677 | expect(err).to.not.be.undefined 678 | } 679 | } 680 | }) 681 | mockGithub(null) 682 | const github = require('../src/github') 683 | expect(github.removeLabelsToPullRequest(payload)).to.eventually.be.false 684 | }) 685 | }) 686 | 687 | it('check param', () => { 688 | mockGithub(null, { 689 | issues: { 690 | removeLabel({owner, repo, number, state}) { 691 | expect(owner).to.equal('xuexb') 692 | expect(repo).to.equal('github-bot') 693 | expect(number).to.equal(1) 694 | expect(assignees).to.equal('ok') 695 | return Promise.resolve() 696 | } 697 | } 698 | }) 699 | const github = require('../src/github') 700 | return github.removeLabelsToPullRequest(payload, 'ok') 701 | }) 702 | }) 703 | 704 | describe('.removeLabelsToIssue', () => { 705 | it('should be a method', () => { 706 | mockGithub(null, { 707 | issues: { 708 | removeLabel() { 709 | return Promise.resolve() 710 | } 711 | } 712 | }) 713 | const github = require('../src/github') 714 | expect(github.removeLabelsToIssue).to.be.a('function') 715 | }) 716 | 717 | describe('should return boolean', () => { 718 | it('true', () => { 719 | mockGithub(null, { 720 | issues: { 721 | removeLabel() { 722 | return Promise.resolve() 723 | } 724 | } 725 | }) 726 | const github = require('../src/github') 727 | expect(github.removeLabelsToIssue(payload, 'label')).to.eventually.be.true 728 | }) 729 | 730 | it('false', () => { 731 | mockGithub(null, { 732 | issues: { 733 | removeLabel() { 734 | throw new TypeError('error') 735 | } 736 | } 737 | }) 738 | const github = require('../src/github') 739 | expect(github.removeLabelsToIssue(payload)).to.eventually.be.false 740 | }) 741 | 742 | it('error', () => { 743 | mock('../src/logger', { 744 | appLog: { 745 | error(err) { 746 | expect(err).to.not.be.undefined 747 | } 748 | } 749 | }) 750 | mockGithub(null) 751 | const github = require('../src/github') 752 | expect(github.removeLabelsToIssue(payload)).to.eventually.be.false 753 | }) 754 | }) 755 | 756 | it('check param', () => { 757 | mockGithub(null, { 758 | issues: { 759 | removeLabel({owner, repo, number, state}) { 760 | expect(owner).to.equal('xuexb') 761 | expect(repo).to.equal('github-bot') 762 | expect(number).to.equal(1) 763 | expect(assignees).to.equal('ok') 764 | return Promise.resolve() 765 | } 766 | } 767 | }) 768 | const github = require('../src/github') 769 | return github.removeLabelsToIssue(payload, 'ok') 770 | }) 771 | }) 772 | 773 | describe('.createRelease', () => { 774 | it('should be a method', () => { 775 | mockGithub(null, { 776 | repos: { 777 | createRelease() { 778 | return Promise.resolve() 779 | } 780 | } 781 | }) 782 | const github = require('../src/github') 783 | expect(github.createRelease).to.be.a('function') 784 | }) 785 | 786 | describe('should return boolean', () => { 787 | it('true', () => { 788 | mockGithub(null, { 789 | repos: { 790 | createRelease() { 791 | return Promise.resolve() 792 | } 793 | } 794 | }) 795 | const github = require('../src/github') 796 | expect(github.createRelease(payload, {})).to.eventually.be.true 797 | }) 798 | 799 | it('false', () => { 800 | mockGithub(null, { 801 | repos: { 802 | createRelease() { 803 | throw new TypeError('error') 804 | } 805 | } 806 | }) 807 | const github = require('../src/github') 808 | expect(github.createRelease(payload)).to.eventually.be.false 809 | }) 810 | 811 | it('error', () => { 812 | mock('../src/logger', { 813 | appLog: { 814 | error(err) { 815 | expect(err).to.not.be.undefined 816 | } 817 | } 818 | }) 819 | mockGithub(null) 820 | const github = require('../src/github') 821 | expect(github.createRelease(payload)).to.eventually.be.false 822 | }) 823 | }) 824 | 825 | it('check param', () => { 826 | mockGithub(null, { 827 | repos: { 828 | createRelease({owner, repo, number, tag_name, target_commitish, name, body, draft, prerelease}) { 829 | expect(owner).to.equal('xuexb') 830 | expect(repo).to.equal('github-bot') 831 | expect(number).to.equal(1) 832 | expect(tag_name).to.equal('tag_name') 833 | expect(target_commitish).to.equal('target_commitish') 834 | expect(name).to.equal('name') 835 | expect(body).to.equal('body') 836 | expect(draft).to.equal('draft') 837 | expect(prerelease).to.equal('prerelease') 838 | return Promise.resolve() 839 | } 840 | } 841 | }) 842 | const github = require('../src/github') 843 | return github.createRelease(payload, { 844 | tag_name: 'tag_name', 845 | target_commitish: 'target_commitish', 846 | name: 'name', 847 | body: 'body', 848 | draft: 'draft', 849 | prerelease: 'prerelease' 850 | }) 851 | }) 852 | }) 853 | 854 | describe('.getReleaseByTag', () => { 855 | it('should be a method', () => { 856 | mockGithub(null, { 857 | repos: { 858 | getReleaseByTag() { 859 | return Promise.resolve() 860 | } 861 | } 862 | }) 863 | const github = require('../src/github') 864 | expect(github.getReleaseByTag).to.be.a('function') 865 | }) 866 | 867 | describe('should return boolean', () => { 868 | it('true', () => { 869 | mockGithub(null, { 870 | repos: { 871 | getReleaseByTag() { 872 | return Promise.resolve({ 873 | data: true 874 | }) 875 | } 876 | } 877 | }) 878 | const github = require('../src/github') 879 | expect(github.getReleaseByTag(payload, { 880 | tag_name: 'ok' 881 | })).to.eventually.be.true 882 | }) 883 | 884 | it('false', () => { 885 | mockGithub(null, { 886 | repos: { 887 | getReleaseByTag() { 888 | return Promise.resolve({ 889 | data: false 890 | }) 891 | } 892 | } 893 | }) 894 | const github = require('../src/github') 895 | expect(github.getReleaseByTag(payload)).to.eventually.be.false 896 | }) 897 | 898 | it('error', () => { 899 | mock('../src/logger', { 900 | appLog: { 901 | error(err) { 902 | expect(err).to.not.be.undefined 903 | } 904 | } 905 | }) 906 | mockGithub(null) 907 | const github = require('../src/github') 908 | expect(github.getReleaseByTag(payload)).to.eventually.be.null 909 | }) 910 | }) 911 | 912 | it('check param', () => { 913 | mockGithub(null, { 914 | repos: { 915 | getReleaseByTag({owner, repo, number, name}) { 916 | expect(owner).to.equal('xuexb') 917 | expect(repo).to.equal('github-bot') 918 | expect(number).to.equal(1) 919 | expect(name).to.equal('tag_name') 920 | return Promise.resolve() 921 | } 922 | } 923 | }) 924 | const github = require('../src/github') 925 | return github.getReleaseByTag(payload, { 926 | tag_name: 'tag_name' 927 | }) 928 | }) 929 | }) 930 | 931 | describe('.createReviewRequest', () => { 932 | it('should be a method', () => { 933 | mockGithub(null, { 934 | pullRequests: { 935 | createReviewRequest() { 936 | return Promise.resolve() 937 | } 938 | } 939 | }) 940 | const github = require('../src/github') 941 | expect(github.createReviewRequest).to.be.a('function') 942 | }) 943 | 944 | describe('should return boolean', () => { 945 | it('true', () => { 946 | mockGithub(null, { 947 | pullRequests: { 948 | createReviewRequest() { 949 | return Promise.resolve() 950 | } 951 | } 952 | }) 953 | const github = require('../src/github') 954 | expect(github.createReviewRequest(payload, { 955 | reviewers: 'reviewers', 956 | team_reviewers: 'team_reviewers' 957 | })).to.eventually.be.true 958 | }) 959 | 960 | it('false', () => { 961 | mockGithub(null, { 962 | pullRequests: { 963 | createReviewRequest() { 964 | throw new TypeError('error') 965 | } 966 | } 967 | }) 968 | const github = require('../src/github') 969 | expect(github.createReviewRequest(payload)).to.eventually.be.false 970 | }) 971 | 972 | it('error', () => { 973 | mock('../src/logger', { 974 | appLog: { 975 | error(err) { 976 | expect(err).to.not.be.undefined 977 | } 978 | } 979 | }) 980 | mockGithub(null) 981 | const github = require('../src/github') 982 | expect(github.createReviewRequest(payload)).to.eventually.be.false 983 | }) 984 | }) 985 | 986 | it('check param', () => { 987 | mockGithub(null, { 988 | pullRequests: { 989 | createReviewRequest({owner, repo, number, team_reviewers, reviewers}) { 990 | expect(owner).to.equal('xuexb') 991 | expect(repo).to.equal('github-bot') 992 | expect(number).to.equal(1) 993 | expect(reviewers).to.equal('reviewers') 994 | expect(team_reviewers).to.equal('team_reviewers') 995 | return Promise.resolve() 996 | } 997 | } 998 | }) 999 | const github = require('../src/github') 1000 | return github.createReviewRequest(payload, { 1001 | reviewers: 'reviewers', 1002 | team_reviewers: 'team_reviewers' 1003 | }) 1004 | }) 1005 | }) 1006 | 1007 | describe('.getTags', () => { 1008 | it('should be a method', () => { 1009 | mockGithub(null, { 1010 | repos: { 1011 | getTags() { 1012 | return Promise.resolve() 1013 | } 1014 | } 1015 | }) 1016 | const github = require('../src/github') 1017 | expect(github.getTags).to.be.a('function') 1018 | }) 1019 | 1020 | describe('should return boolean', () => { 1021 | it('true', () => { 1022 | mockGithub(null, { 1023 | repos: { 1024 | getTags() { 1025 | return Promise.resolve({ 1026 | data: true 1027 | }) 1028 | } 1029 | } 1030 | }) 1031 | const github = require('../src/github') 1032 | expect(github.getTags(payload)).to.eventually.be.true 1033 | }) 1034 | 1035 | it('false', () => { 1036 | mockGithub(null, { 1037 | repos: { 1038 | getTags() { 1039 | return Promise.resolve({ 1040 | data: false 1041 | }) 1042 | } 1043 | } 1044 | }) 1045 | const github = require('../src/github') 1046 | expect(github.getTags(payload)).to.eventually.be.false 1047 | }) 1048 | 1049 | it('error', () => { 1050 | mock('../src/logger', { 1051 | appLog: { 1052 | error(err) { 1053 | expect(err).to.not.be.undefined 1054 | } 1055 | } 1056 | }) 1057 | mockGithub(null) 1058 | const github = require('../src/github') 1059 | expect(github.getTags(payload)).to.eventually.be.deep.equal([]) 1060 | }) 1061 | }) 1062 | 1063 | it('check param', () => { 1064 | mockGithub(null, { 1065 | repos: { 1066 | getTags({owner, repo}) { 1067 | expect(owner).to.equal('xuexb') 1068 | expect(repo).to.equal('github-bot') 1069 | return Promise.resolve() 1070 | } 1071 | } 1072 | }) 1073 | const github = require('../src/github') 1074 | return github.getTags(payload) 1075 | }) 1076 | }) 1077 | 1078 | describe('.compareCommits', () => { 1079 | it('should be a method', () => { 1080 | mockGithub(null, { 1081 | repos: { 1082 | compareCommits() { 1083 | return Promise.resolve() 1084 | } 1085 | } 1086 | }) 1087 | const github = require('../src/github') 1088 | expect(github.compareCommits).to.be.a('function') 1089 | }) 1090 | 1091 | describe('should return boolean', () => { 1092 | it('true', () => { 1093 | mockGithub(null, { 1094 | repos: { 1095 | compareCommits() { 1096 | return Promise.resolve({ 1097 | data: true 1098 | }) 1099 | } 1100 | } 1101 | }) 1102 | const github = require('../src/github') 1103 | expect(github.compareCommits(payload, { 1104 | base: 'base', 1105 | head: 'head' 1106 | })).to.eventually.be.true 1107 | }) 1108 | 1109 | it('false', () => { 1110 | mockGithub(null, { 1111 | repos: { 1112 | compareCommits() { 1113 | return Promise.resolve({ 1114 | data: false 1115 | }) 1116 | } 1117 | } 1118 | }) 1119 | const github = require('../src/github') 1120 | expect(github.compareCommits(payload)).to.eventually.be.false 1121 | }) 1122 | 1123 | it('error', () => { 1124 | mock('../src/logger', { 1125 | appLog: { 1126 | error(err) { 1127 | expect(err).to.not.be.undefined 1128 | } 1129 | } 1130 | }) 1131 | mockGithub(null) 1132 | const github = require('../src/github') 1133 | expect(github.compareCommits(payload)).to.eventually.be.null 1134 | }) 1135 | }) 1136 | 1137 | it('check param', () => { 1138 | mockGithub(null, { 1139 | repos: { 1140 | compareCommits({owner, repo, base, head}) { 1141 | expect(owner).to.equal('xuexb') 1142 | expect(repo).to.equal('github-bot') 1143 | expect(base).to.equal('base') 1144 | expect(head).to.equal('head') 1145 | return Promise.resolve() 1146 | } 1147 | } 1148 | }) 1149 | const github = require('../src/github') 1150 | return github.compareCommits(payload, { 1151 | base: 'base', 1152 | head: 'head' 1153 | }) 1154 | }) 1155 | }) 1156 | }) 1157 | -------------------------------------------------------------------------------- /test/modules/issues/autoAssign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file modules/issues/autoAssign.js test case 3 | * @author xuexb 4 | */ 5 | 6 | const expect = require('chai').expect 7 | const mock = require('mock-require') 8 | mock.stopAll() 9 | const clean = require('../../utils/clean') 10 | 11 | describe('modules/issues/autoAssign.js', () => { 12 | beforeEach('clear node cache', () => { 13 | clean('src/github') 14 | clean('src/utils') 15 | clean('src/modules/issues/autoAssign') 16 | 17 | mock('../../../src/utils', { 18 | getPkgConfig() { 19 | return {} 20 | } 21 | }) 22 | mock('../../../src/github', { 23 | addAssigneesToIssue() { 24 | } 25 | }) 26 | }) 27 | 28 | it('event name', () => { 29 | const autoAssign = require('../../../src/modules/issues/autoAssign') 30 | autoAssign(name => { 31 | expect(name).to.equal('issues_labeled') 32 | }) 33 | }) 34 | 35 | describe('set label', () => { 36 | it('is ok', (done) => { 37 | mock('../../../src/utils', { 38 | getPkgConfig() { 39 | return { 40 | labelToAuthor: { 41 | autoAssign: 'github-bot' 42 | } 43 | } 44 | } 45 | }) 46 | mock('../../../src/github', { 47 | addAssigneesToIssue(payload, label) { 48 | expect(payload).to.be.a('object').and.not.empty 49 | expect(label).to.equal('github-bot') 50 | done() 51 | } 52 | }) 53 | 54 | const autoAssign = require('../../../src/modules/issues/autoAssign') 55 | autoAssign(function (name, callback) { 56 | callback({ 57 | payload: { 58 | label: { 59 | name: 'autoAssign' 60 | } 61 | } 62 | }) 63 | }) 64 | }) 65 | 66 | it('is false', (done) => { 67 | mock('../../../src/github', { 68 | addAssigneesToIssue() { 69 | done('error') 70 | } 71 | }) 72 | 73 | const autoAssign = require('../../../src/modules/issues/autoAssign') 74 | autoAssign(function (name, callback) { 75 | callback({ 76 | payload: { 77 | label: { 78 | name: 'error' 79 | } 80 | } 81 | }) 82 | }) 83 | setTimeout(done) 84 | }) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /test/modules/issues/autoLabel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file modules/issues/autoLabel.js test case 3 | * @author xuexb 4 | */ 5 | 6 | const expect = require('chai').expect 7 | const mock = require('mock-require') 8 | mock.stopAll() 9 | const clean = require('../../utils/clean') 10 | 11 | describe('modules/issues/autoLabel.js', () => { 12 | beforeEach('clear node cache', () => { 13 | clean('src/github') 14 | clean('src/modules/issues/autoLabel') 15 | 16 | mock('../../../src/github', { 17 | addLabelsToIssue() { 18 | } 19 | }) 20 | }) 21 | 22 | it('event name', () => { 23 | const autoLabel = require('../../../src/modules/issues/autoLabel') 24 | autoLabel(name => { 25 | expect(name).to.equal('issues_opened') 26 | }) 27 | }) 28 | 29 | it('get label success', (done) => { 30 | mock('../../../src/github', { 31 | addLabelsToIssue(payload, label) { 32 | expect(payload).to.be.a('object').and.not.empty 33 | expect(label).to.equal('github-bot') 34 | done() 35 | } 36 | }) 37 | 38 | const autoLabel = require('../../../src/modules/issues/autoLabel') 39 | autoLabel((name, callback) => { 40 | callback({ 41 | payload: { 42 | issue: { 43 | body: '我是测试内容\n测试' 44 | } 45 | } 46 | }) 47 | }) 48 | }) 49 | 50 | it('get label error', (done) => { 51 | mock('../../../src/github', { 52 | addLabelsToIssue() { 53 | done('error') 54 | } 55 | }) 56 | 57 | const autoLabel = require('../../../src/modules/issues/autoLabel') 58 | autoLabel((name, callback) => { 59 | callback({ 60 | payload: { 61 | issue: { 62 | body: '我是测试内容' 63 | } 64 | } 65 | }) 66 | }) 67 | setTimeout(done) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file utils.js test case 3 | * @author xuexb 4 | */ 5 | require('mock-require').stopAll() 6 | const utils = require('../src/utils') 7 | const expect = require('chai').expect 8 | 9 | describe('utils.js', () => { 10 | describe('.toArray', () => { 11 | it('should return self if empty', () => { 12 | expect(utils.toArray()).to.be.undefined 13 | expect(utils.toArray('')).to.equal('') 14 | expect(utils.toArray(null)).to.be.null 15 | }) 16 | it('should return array if not the empty string', () => { 17 | expect(utils.toArray(['string'])).to.be.a('array').and.to.deep.equal(['string']) 18 | expect(utils.toArray('string')).to.be.a('array').and.to.deep.equal(['string']) 19 | }) 20 | }) 21 | 22 | describe('.getPkgConfig', () => { 23 | it('should return object', () => { 24 | expect(utils.getPkgConfig()).to.be.a('object').and.to.not.empty 25 | }) 26 | }) 27 | 28 | describe('.getPkgCommitPrefix', () => { 29 | it('should return array', () => { 30 | expect(utils.getPkgCommitPrefix()).to.be.a('array').and.to.not.empty 31 | }) 32 | }) 33 | 34 | it('.verifySignature', () => { 35 | const GITHUB_SECRET_TOKEN = process.env['GITHUB_SECRET_TOKEN'] 36 | 37 | process.env['GITHUB_SECRET_TOKEN'] = 'test' 38 | const flag = utils.verifySignature({ 39 | rawBody: 'test', 40 | headers: { 41 | 'x-hub-signature': 'test' 42 | } 43 | }) 44 | process.env['GITHUB_SECRET_TOKEN'] = GITHUB_SECRET_TOKEN 45 | 46 | expect(flag).to.be.false 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/utils/clean.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 清除 node 缓存,以根目录为基础路径 3 | * @author xuexb 4 | */ 5 | 6 | const resolve = require('path').resolve 7 | const extname = require('path').extname 8 | 9 | module.exports = path => { 10 | delete require.cache[resolve(__dirname, '../../', path) + (extname(path) === '' ? '.js' : '')] 11 | } 12 | --------------------------------------------------------------------------------