lrf402788946 4 years ago
commit
0a312c481f

+ 29 - 0
.autod.conf.js

@@ -0,0 +1,29 @@
+'use strict';
+
+module.exports = {
+  write: true,
+  prefix: '^',
+  plugin: 'autod-egg',
+  test: [
+    'test',
+    'benchmark',
+  ],
+  dep: [
+    'egg',
+    'egg-scripts',
+  ],
+  devdep: [
+    'egg-ci',
+    'egg-bin',
+    'egg-mock',
+    'autod',
+    'autod-egg',
+    'eslint',
+    'eslint-config-egg',
+  ],
+  exclude: [
+    './test/fixtures',
+    './dist',
+  ],
+};
+

+ 1 - 0
.eslintignore

@@ -0,0 +1 @@
+coverage

+ 3 - 0
.eslintrc

@@ -0,0 +1,3 @@
+{
+  "extends": "eslint-config-egg"
+}

+ 42 - 0
.github/workflows/nodejs.yml

@@ -0,0 +1,42 @@
+# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
+name: Node.js CI
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+  schedule:
+    - cron: '0 2 * * *'
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        node-version: [10]
+        os: [ubuntu-latest, windows-latest, macos-latest]
+
+    steps:
+    - name: Checkout Git Source
+      uses: actions/checkout@v2
+
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+
+    - name: Install Dependencies
+      run: npm i -g npminstall && npminstall
+
+    - name: Continuous Integration
+      run: npm run ci
+
+    - name: Code Coverage
+      uses: codecov/codecov-action@v1
+      with:
+        token: ${{ secrets.CODECOV_TOKEN }}

+ 14 - 0
.gitignore

@@ -0,0 +1,14 @@
+logs/
+npm-debug.log
+yarn-error.log
+node_modules/
+package-lock.json
+yarn.lock
+coverage/
+.idea/
+run/
+.DS_Store
+*.sw*
+*.un~
+typings/
+.nyc_output/

+ 12 - 0
.travis.yml

@@ -0,0 +1,12 @@
+
+language: node_js
+node_js:
+  - '10'
+before_install:
+  - npm i npminstall -g
+install:
+  - npminstall
+script:
+  - npm run ci
+after_script:
+  - npminstall codecov && codecov

+ 33 - 0
README.md

@@ -0,0 +1,33 @@
+# service-auth
+
+权限角色
+
+## QuickStart
+
+<!-- add docs here for user -->
+
+see [egg docs][egg] for more detail.
+
+### Development
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### Deploy
+
+```bash
+$ npm start
+$ npm stop
+```
+
+### npm scripts
+
+- Use `npm run lint` to check code style.
+- Use `npm test` to run unit test.
+- Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail.
+
+
+[egg]: https://eggjs.org

+ 46 - 0
app/controller/.menu.js

@@ -0,0 +1,46 @@
+module.exports = {
+  create: {
+    requestBody: ["!project", "type", "title",'route','pid', "disabled",'sort', "params"],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  update: {
+    params: ["!id"],
+    requestBody: ["!project", "type", "title",'route','pid', "disabled",'sort', "params"],
+  },
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        project: "project",
+        type: "type",
+        title: "title",
+        route:'route',
+        pid:'pid',
+        disabled: "disabled",
+      },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["sort"],
+      desc: true,
+      count: true,
+    },
+  },
+  findProject: {
+    parameters: {
+      params: ["!project"],
+      query: ["type"],
+    },
+
+    service: "findProject",
+  },
+};

+ 41 - 0
app/controller/.role.js

@@ -0,0 +1,41 @@
+module.exports = {
+  create: {
+    requestBody: ["!project", "!name", "type", "menu", "disabled", "params"],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  update: {
+    params: ["!id"],
+    requestBody: ["!project", "!name", "type", "menu", "disabled", "params"],
+  },
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        project: "project",
+        name: "name",
+        type: "type",
+        menu: "menu",
+        disabled: "disabled",
+      },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["meta.createdAt"],
+      asc: true,
+      count: true,
+    },
+  },
+  roleMenuTree: {
+    parameters: { query: { project: "project", type: "type", userid: 'userid' } },
+    service: "getRoleMenuTree",
+  },
+};

+ 44 - 0
app/controller/.user-menu.js

@@ -0,0 +1,44 @@
+module.exports = {
+  create: {
+    requestBody: ["!project", "!userid", "menu", "params"],
+  },
+  destroy: {
+    params: ["!id"],
+    service: "delete",
+  },
+  update: {
+    params: ["!id"],
+    requestBody: ["!project", "!userid", "menu", "params"],
+  },
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        project: "project",
+        userid: "userid",
+      },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["meta.createdAt"],
+      asc: true,
+      count: true,
+    },
+  },
+  getUserMenu: {
+    parameters: {
+      query: {
+        project: "project",
+        type: "type",
+        userid: "userid",
+      },
+    },
+    service: "getUserMenu",
+  },
+};

+ 12 - 0
app/controller/home.js

@@ -0,0 +1,12 @@
+'use strict';
+
+const Controller = require('egg').Controller;
+
+class HomeController extends Controller {
+  async index() {
+    const { ctx } = this;
+    ctx.body = 'hi, egg';
+  }
+}
+
+module.exports = HomeController;

+ 16 - 0
app/controller/menu.js

@@ -0,0 +1,16 @@
+'use strict';
+
+const _ = require('lodash');
+const meta = require('./.menu.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose/lib/controller');
+
+// 教师申请讲课表管理
+class MenuController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.menu;
+  }
+}
+
+module.exports = CrudController(MenuController, meta);

+ 16 - 0
app/controller/role.js

@@ -0,0 +1,16 @@
+'use strict';
+
+const _ = require('lodash');
+const meta = require('./.role.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose/lib/controller');
+
+// 教师申请讲课表管理
+class RoleController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.role;
+  }
+}
+
+module.exports = CrudController(RoleController, meta);

+ 16 - 0
app/controller/user-menu.js

@@ -0,0 +1,16 @@
+'use strict';
+
+const _ = require('lodash');
+const meta = require('./.user-menu.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose/lib/controller');
+
+// 教师申请讲课表管理
+class UserMenuController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.userMenu;
+  }
+}
+
+module.exports = CrudController(UserMenuController, meta);

+ 22 - 0
app/model/menu.js

@@ -0,0 +1,22 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
+
+const Menu = {
+  project: { type: String, required: true, maxLength: 200 }, // 项目名
+  title: { type: String, required: true }, // 菜单标题
+  route: { type: String, required: false, maxLength: 200 }, // 路由
+  pid: { type: String, required: false, maxLength: 200 }, // 上级id
+  disabled: { type: Boolean, default: false }, // 使用状态
+  sort: { type: Number, required: false, default: 0 }, // 顺序,默认为0,使用降序排序,数值越大在前面
+  params: { type: Object }, // 参数字段,需要就用,不需要就不用
+  type: { type: String, required: false, maxLength: 200 }, // 类型,每个项目有每个项目的设置,这里只存储,让各自项目记住自己类型对应什么
+};
+const schema = new Schema(Menu, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.plugin(metaPlugin);
+
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Menu', schema, 'menu');
+};

+ 20 - 0
app/model/role.js

@@ -0,0 +1,20 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
+
+const Role = {
+  project: { type: String, required: true, maxLength: 200 }, // 项目名
+  name: { type: String, required: true, maxLength: 200 }, // 角色名称
+  type: { type: String, required: false, maxLength: 200 }, // 类型,每个项目有每个项目的设置,这里只存储,让各自项目记住自己类型对应什么
+  menu: { type: Array, required: true }, // 菜单标题
+  disabled: { type: Boolean, default: false }, // 使用状态
+  params: { type: Object }, // 参数字段,需要就用,不需要就不用
+};
+const schema = new Schema(Role, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.plugin(metaPlugin);
+
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Role', schema, 'role');
+};

+ 18 - 0
app/model/user-menu.js

@@ -0,0 +1,18 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
+// 用户特定权限
+const UserMenu = {
+  project: { type: String, required: true, maxLength: 200 }, // 项目名
+  userid: { type: String, required: true, maxLength: 200 }, // 用户id
+  menu: { type: Array, required: true }, // 菜单标题
+  params: { type: Object }, // 参数字段,需要就用,不需要就不用
+};
+const schema = new Schema(UserMenu, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+schema.plugin(metaPlugin);
+
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('UserMenu', schema, 'user_menu');
+};

+ 18 - 0
app/router.js

@@ -0,0 +1,18 @@
+'use strict';
+
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+  const { router, controller } = app;
+  router.get('/', controller.home.index);
+  router.get('menu', '/api/auth/menu/project/:project', controller.menu.findProject);
+  router.resources('menu', '/api/auth/menu', controller.menu); // index、create、show、destroy
+  router.post('menu', '/api/auth/menu/update/:id', controller.menu.update);
+  router.get('role', '/api/auth/role/menu/tree', controller.role.roleMenuTree); // index、create、show、destroy
+  router.resources('role', '/api/auth/role', controller.role); // index、create、show、destroy
+  router.post('role', '/api/auth/role/update/:id', controller.role.update);
+  router.get('usermenu', '/api/auth/usermenu/menu', controller.userMenu.getUserMenu);
+  router.resources('usermenu', '/api/auth/usermenu', controller.userMenu); // index、create、show、destroy
+  router.post('usermenu', '/api/auth/usermenu/update/:id', controller.userMenu.update);
+};

+ 64 - 0
app/service/menu.js

@@ -0,0 +1,64 @@
+'use strict';
+
+const assert = require('assert');
+const _ = require('lodash');
+const moment = require('moment');
+const { ObjectId } = require('mongoose').Types;
+const { CrudService } = require('naf-framework-mongoose/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+
+class MenuService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'menu');
+    this.model = this.ctx.model.Menu;
+  }
+
+  async delete({ id }) {
+    let children = await this.dbFindChildren(id);
+    children = children.map(i => ObjectId(i._id));
+    children.push(ObjectId(id));
+    const res = await this.model.deleteMany({ _id: children });
+    return res;
+  }
+
+  async dbFindChildren(pid) {
+    let toReturn = [];
+    const res = await this.model.find({ pid }, '_id');
+    toReturn = [ ...toReturn, ...res ];
+    if (res.length > 0) {
+      for (const i of res) {
+        const { _id } = i;
+        const list = await this.dbFindChildren(_id);
+        toReturn = [ ...toReturn, ...list ];
+      }
+    }
+    return toReturn;
+  }
+
+  async findProject(condition) {
+    const res = await this.model.find(condition).sort({ sort: -1 });
+    let list = [];
+    if (res && res.length > 0) {
+      list = await this.toFindChildren(res);
+    }
+    return list;
+  }
+
+  toFindChildren(list) {
+    list = JSON.parse(JSON.stringify(list));
+    const head = list.filter(f => !f.pid);
+    if (head.length <= 0) throw new BusinessError(ErrorCode.DATA_INVALID, '需要将根目录加入整合函数的参数中');
+    return this.findChildren(head, list);
+  }
+
+  findChildren(list, data) {
+    for (const i of list) {
+      let children = data.filter(f => f.pid === i._id);
+      if (children.length > 0) children = this.findChildren(children, data);
+      i.children = children;
+    }
+    return list;
+  }
+}
+
+module.exports = MenuService;

+ 52 - 0
app/service/role.js

@@ -0,0 +1,52 @@
+'use strict';
+
+const assert = require('assert');
+const _ = require('lodash');
+const moment = require('moment');
+const { ObjectId } = require('mongoose').Types;
+const { CrudService } = require('naf-framework-mongoose/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+
+class RoleService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'role');
+    this.model = this.ctx.model.Role;
+    this.Menumodel = this.ctx.model.Menu;
+  }
+
+  async getRoleMenuTree({ project, type, userid }) {
+    assert(project, '缺少需要查找的项目信息');
+    assert(type, '缺少需要查找角色代码');
+    // 找到这个角色的所有权限
+    const res = await this.model.findOne({ project, type });
+    if (!res) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定角色');
+    let { menu } = res;
+    const object = {};
+    // 找到用户的角色对应的权限
+    let roleMenu = await this.Menumodel.find({ project }).sort({ sort: -1 });
+    if (roleMenu.length > 0) {
+      // 将用户角色部分得到权限限制住,不允许改,必须有
+      roleMenu = JSON.parse(JSON.stringify(roleMenu));
+      roleMenu = roleMenu.map(i => {
+        const { _id } = i;
+        if (menu.includes(_id)) i.disabled = true;
+        return i;
+      });
+      // 整理成树状图格式
+      roleMenu = await this.ctx.service.menu.toFindChildren(roleMenu);
+    }
+    object.menu = roleMenu;
+    // 如果有userid,说明还需要找到这个用户的特有的权限,合并到selected中
+    if (userid) {
+      const umRes = await this.ctx.service.userMenu.findUserMenu({ project, userid });
+      if (umRes) {
+        const { menu: uMenu } = umRes;
+        menu = [ ...menu, ...uMenu ];
+      }
+    }
+    object.selected = menu;
+    return object;
+  }
+}
+
+module.exports = RoleService;

+ 70 - 0
app/service/user-menu.js

@@ -0,0 +1,70 @@
+'use strict';
+
+const assert = require('assert');
+const _ = require('lodash');
+const moment = require('moment');
+const { ObjectId } = require('mongoose').Types;
+const { CrudService } = require('naf-framework-mongoose/lib/service');
+const { BusinessError, ErrorCode } = require('naf-core').Error;
+
+class UserMenuService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'usermenu');
+    this.model = this.ctx.model.UserMenu;
+    this.Rolemodel = this.ctx.model.Role;
+    this.Menumodel = this.ctx.model.Menu;
+  }
+
+  async create(data) {
+    const { project, userid } = data;
+    let res;
+    res = await this.model.findOne({ project, userid });
+    if (res) {
+      res = await this.model.update({ project, userid }, data);
+    } else {
+      res = await this.model.create(data);
+    }
+    return res;
+  }
+
+  async findUserMenu({ project, userid }) {
+    assert(project, '缺少需要查找的项目信息');
+    assert(userid, '缺少用户信息');
+    const res = await this.model.findOne({ project, userid });
+    return res;
+  }
+
+  async getUserMenu({ project, type, userid }) {
+    assert(project, '缺少需要查找的项目信息');
+    assert(type, '缺少需要查找角色代码');
+    assert(userid, '缺少需要查找用户信息');
+    // 找到这个角色的权限,找到用户权限,合并在一起
+    // 找到这个角色的所有权限
+    const menuids = [];
+    const res = await this.Rolemodel.findOne({ project, type });
+    if (!res) { throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到指定角色'); }
+    const { menu: roleMenu } = res;
+    // roleMenu只存id
+    menuids.push(...roleMenu);
+    // 找到用户的特殊权限
+    const umidsRes = await this.ctx.service.userMenu.findUserMenu({ project, userid });
+    if (umidsRes) {
+      const { menu: uMenuids } = umidsRes;
+      // uMenuids只存id
+      menuids.push(...uMenuids);
+    }
+    // 找到所有菜单的父级,下面过滤用,把父级目录拿出来(不单纯是根目录)
+    const parentIdsRes = await this.Menumodel.find({ _id: menuids.map(i => ObjectId(i)) }, 'pid');
+    let parentIds = parentIdsRes.map(i => i.pid);
+    parentIds = _.compact(parentIds);
+    menuids.push(...parentIds);
+    // 找到该项目的所有的权限
+    const allMenu = await this.Menumodel.find({ project, disabled: false }).sort({ sort: -1 });
+    // 需要将根目录整理出来,将
+    const dup = allMenu.filter(f => menuids.find(um => ObjectId(um).equals(f._id)));
+    const userMenu = await this.ctx.service.menu.toFindChildren(dup);
+    return userMenu;
+  }
+}
+
+module.exports = UserMenuService;

+ 14 - 0
appveyor.yml

@@ -0,0 +1,14 @@
+environment:
+  matrix:
+    - nodejs_version: '10'
+
+install:
+  - ps: Install-Product node $env:nodejs_version
+  - npm i npminstall && node_modules\.bin\npminstall
+
+test_script:
+  - node --version
+  - npm --version
+  - npm run test
+
+build: off

+ 80 - 0
config/config.default.js

@@ -0,0 +1,80 @@
+/* eslint valid-jsdoc: "off" */
+
+'use strict';
+
+/**
+ * @param {Egg.EggAppInfo} appInfo app info
+ */
+module.exports = appInfo => {
+  /**
+   * built-in config
+   * @type {Egg.EggAppConfig}
+   **/
+  const config = exports = {};
+
+  // use for cookie sign key, should change to your own and keep security
+  config.keys = appInfo.name + '_1604310853516_4870';
+
+  // add your middleware config here
+  config.middleware = [];
+
+  // add your user config here
+  const userConfig = {
+    // myAppName: 'egg',
+  };
+  config.cluster = {
+    listen: {
+      port: 7006,
+    },
+  };
+  // mq配置
+  config.amqp = {
+    client: {
+      hostname: '127.0.0.1',
+      username: 'visit',
+      password: 'visit',
+      vhost: 'train',
+    },
+    app: true,
+    agent: true,
+  };
+
+  // redis config
+  config.redis = {
+    client: {
+      port: 6379, // Redis port
+      host: '127.0.0.1', // Redis host
+      password: '',
+      db: 0,
+    },
+  };
+
+  // mongoose config
+  config.mongoose = {
+    url: 'mongodb://127.0.0.1:27017/train',
+    options: {
+      // user: 'admin',
+      // pass: 'admin',
+      // authSource: 'admin',
+      // useNewUrlParser: true,
+      // useCreateIndex: true,
+    },
+  };
+
+  config.project = {
+    center: 'http://127.0.0.1:2001',
+  };
+
+  // 安全配置
+  config.security = {
+    csrf: {
+      // ignoreJSON: true, // 默认为 false,当设置为 true 时,将会放过所有 content-type 为 `application/json` 的请求
+      enable: false,
+    },
+  };
+
+  return {
+    ...config,
+    ...userConfig,
+  };
+};

+ 12 - 0
config/plugin.js

@@ -0,0 +1,12 @@
+'use strict';
+
+/** @type Egg.EggPlugin */
+exports.amqp = {
+  enable: true,
+  package: 'egg-naf-amqp',
+};
+
+exports.redis = {
+  enable: true,
+  package: 'egg-redis',
+};

+ 17 - 0
ecosystem.config.js

@@ -0,0 +1,17 @@
+'use strict';
+
+const app = 'mission';
+module.exports = {
+  apps: [{
+    name: app, // 应用名称
+    script: './server.js', // 实际启动脚本
+    out: `./logs/${app}.log`,
+    error: `./logs/${app}.err`,
+    watch: [ // 监控变化的目录,一旦变化,自动重启
+      'app', 'config',
+    ],
+    env: {
+      NODE_ENV: 'production', // 环境参数,当前指定为生产环境
+    },
+  }],
+};

+ 51 - 0
package.json

@@ -0,0 +1,51 @@
+{
+  "name": "service-auth",
+  "version": "1.0.0",
+  "description": "权限角色",
+  "private": true,
+  "egg": {
+    "framework": "naf-framework-mongoose"
+  },
+  "dependencies": {
+    "egg": "^2.15.1",
+    "egg-scripts": "^2.11.0",
+    "egg-naf-amqp": "0.0.13",
+    "egg-redis": "^2.4.0",
+    "lodash": "^4.17.15",
+    "moment": "^2.27.0",
+    "naf-framework-mongoose": "^0.6.11"
+  },
+  "devDependencies": {
+    "autod": "^3.0.1",
+    "autod-egg": "^1.1.0",
+    "egg-bin": "^4.11.0",
+    "egg-ci": "^1.11.0",
+    "egg-mock": "^3.21.0",
+    "eslint": "^5.13.0",
+    "eslint-config-egg": "^7.1.0"
+  },
+  "engines": {
+    "node": ">=10.0.0"
+  },
+  "scripts": {
+    "start": "egg-scripts start --daemon --title=egg-server-service-auth",
+    "stop": "egg-scripts stop --title=egg-server-service-auth",
+    "dev": "egg-bin dev",
+    "debug": "egg-bin debug",
+    "test": "npm run lint -- --fix && npm run test-local",
+    "test-local": "egg-bin test",
+    "cov": "egg-bin cov",
+    "lint": "eslint .",
+    "ci": "npm run lint && npm run cov",
+    "autod": "autod"
+  },
+  "ci": {
+    "version": "10"
+  },
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "author": "lrf",
+  "license": "MIT"
+}

+ 9 - 0
server.js

@@ -0,0 +1,9 @@
+
+// eslint-disable-next-line strict
+const egg = require('egg');
+
+const workers = Number(process.argv[2] || require('os').cpus().length);
+egg.startCluster({
+  workers,
+  baseDir: __dirname,
+});

+ 20 - 0
test/app/controller/home.test.js

@@ -0,0 +1,20 @@
+'use strict';
+
+const { app, assert } = require('egg-mock/bootstrap');
+
+describe('test/app/controller/home.test.js', () => {
+  it('should assert', () => {
+    const pkg = require('../../../package.json');
+    assert(app.config.keys.startsWith(pkg.name));
+
+    // const ctx = app.mockContext({});
+    // yield ctx.service.xx();
+  });
+
+  it('should GET /', () => {
+    return app.httpRequest()
+      .get('/')
+      .expect('hi, egg')
+      .expect(200);
+  });
+});