├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── History.md ├── LICENSE ├── Readme.md ├── benchmark ├── benchmark.js └── data │ ├── bankList.json │ ├── base.json │ ├── index.json │ ├── ktv │ ├── breadNav.json │ ├── check │ │ └── :orderid.json │ ├── order.json │ ├── orderinfo │ │ └── :id.json │ ├── orders.json │ └── return │ │ └── :type?orderid=:orderid.json │ └── shop │ └── 324483.json ├── bin └── cli.js ├── docs ├── Home.md └── assets │ └── mindmap.png ├── githooks.json ├── githooks └── pre-commit │ └── test ├── index.js ├── package.json └── test ├── fixtures ├── params │ └── :hi.json ├── rpc.json └── simple.json └── mock.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | docs/jsdoc 4 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | test/fixtures/mock/data/response/mockjs.json 2 | test/fixtures/mock/data/response/exception.json 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : true, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "indent" : 4, // {int} Number of spaces to use for indentation 16 | "latedef" : false, // true: Require variables/functions to be defined before being used 17 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 18 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 19 | "noempty" : true, // true: Prohibit use of empty blocks 20 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 21 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 22 | "plusplus" : false, // true: Prohibit use of `++` & `--` 23 | "quotmark" : false, // Quotation mark consistency: 24 | // false : do nothing (default) 25 | // true : ensure whatever is used is consistent 26 | // "single" : require single quotes 27 | // "double" : require double quotes 28 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 29 | "unused" : true, // true: Require all defined variables be used 30 | "strict" : false, // true: Requires all functions run in ES5 Strict Mode 31 | "maxparams" : false, // {int} Max number of formal params allowed per function 32 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 33 | "maxstatements" : false, // {int} Max number statements per function 34 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 35 | "maxlen" : false, // {int} Max number of characters per line 36 | 37 | // Relaxing 38 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 39 | "boss" : true, // true: Tolerate assignments where comparisons would be expected 40 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 41 | "eqnull" : false, // true: Tolerate use of `== null` 42 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 43 | "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) 44 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 45 | // (ex: `for each`, multiple try/catch, function expression…) 46 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 47 | "expr" : true, // true: Tolerate `ExpressionStatement` as Programs 48 | "funcscope" : false, // true: Tolerate defining variables inside control statements 49 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 50 | "iterator" : false, // true: Tolerate using the `__iterator__` property 51 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 52 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 53 | "laxcomma" : false, // true: Tolerate comma-first style coding 54 | "loopfunc" : false, // true: Tolerate functions being defined in loops 55 | "multistr" : false, // true: Tolerate multi-line strings 56 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 57 | "notypeof" : false, // true: Tolerate invalid typeof operator values 58 | "proto" : false, // true: Tolerate using the `__proto__` property 59 | "scripturl" : false, // true: Tolerate script-targeted URLs 60 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 61 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 62 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 63 | "validthis" : false, // true: Tolerate using this in a non-constructor function 64 | 65 | // Environments 66 | "browser" : false, // Web Browser (window, document, etc) 67 | "browserify" : false, // Browserify (node.js code in the browser) 68 | "couch" : false, // CouchDB 69 | "devel" : true, // Development/debugging (alert, confirm, etc) 70 | "dojo" : false, // Dojo Toolkit 71 | "jasmine" : false, // Jasmine 72 | "jquery" : false, // jQuery 73 | "mocha" : true, // Mocha 74 | "mootools" : false, // MooTools 75 | "node" : true, // Node.js 76 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 77 | "prototypejs" : false, // Prototype and Scriptaculous 78 | "qunit" : false, // QUnit 79 | "rhino" : false, // Rhino 80 | "shelljs" : false, // ShellJS 81 | "worker" : false, // Web Workers 82 | "wsh" : false, // Windows Scripting Host 83 | "yui" : false, // Yahoo User Interface 84 | 85 | // Custom Globals 86 | "globals" : { 87 | "fixtures": true, 88 | "xdescribe": true 89 | } // additional predefined global variables 90 | } 91 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.10" 5 | script: "npm run test-travis" 6 | after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" 7 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 3.5.0 / 2015-03-25 2 | ================== 3 | 4 | * Add `mget` to get multiple simulate data in one invocation 5 | 6 | 3.1.0 / 2015-01-26 7 | ================== 8 | 9 | * Rename `match.wildcards` to `match.params`; Rename `option.params` to `option.overrides` 10 | 11 | 3.0.0 / 2015-01-26 12 | ================== 13 | 14 | * Migrate atom and mixin to mock-response 15 | 16 | 2.5.2 / 2015-01-23 17 | ================== 18 | 19 | * Add override for magic params 20 | 21 | 2.5.1 / 2015-01-21 22 | ================== 23 | 24 | * Fix mixin property not being overrided 25 | 26 | 2.5.0 / 2015-01-20 27 | ================== 28 | 29 | * Seperate request, response, locator into mock-request2, 30 | mock-response and mock-locator 31 | 32 | 2.3.0 / 2015-01-12 33 | ================== 34 | 35 | * Handle invalid JSON file 36 | 37 | 2.2.0 / 2014-12-26 38 | ================== 39 | 40 | * Support Node.js module and handlebar template as response file 41 | 42 | 2.1.0 / 2014-12-19 43 | ================== 44 | 45 | * Deprecate file type 46 | 47 | 2.0.0 / 2014-12-18 48 | ================== 49 | 50 | * Add atom support 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (C) 2015 zhongchiyu 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the "Software"), 6 | to deal in the Software without restriction, including without limitation 7 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | and/or sell copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 20 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Monkey [![NPM version][npm-image]][npm-url] [![build status][travis-image]][travis-url] [![Test coverage][coveralls-image]][coveralls-url] 2 | 3 | > Data mapping system 4 | 5 | ## Installation 6 | 7 | npm install --save monkeyjs 8 | 9 | 10 | ## Usage 11 | 12 | var Mock = require('monkeyjs'); 13 | // create mock with data directory 14 | var mock = new Mock('./data-dir'); 15 | // get mock data by HTTP request 16 | var data = mock.get({ 17 | "uri": "/deal/123456", 18 | "method": "POST", 19 | }); 20 | 21 | For implementation detail, see [docs](docs/Home.md), for more example, see [test](./test). 22 | 23 | 24 | ## More 25 | 26 | * test: npm test 27 | * coverage: npm run test-cov 28 | * benchmark: npm run benchmark 29 | 30 | 31 | ## Contribute 32 | 33 | 1. Install [git-hooks](https://github.com/git-hooks/git-hooks) 34 | 2. Execute `git hooks install` 35 | 3. Write source code 36 | 4. Write unit test 37 | 5. Create pull request 38 | 39 | 40 | [npm-image]: https://img.shields.io/npm/v/monkeyjs.svg?style=flat 41 | [npm-url]: https://npmjs.org/package/monkeyjs 42 | [travis-image]: https://img.shields.io/travis/meituan/monkey.svg?style=flat 43 | [travis-url]: https://travis-ci.org/meituan/monkey 44 | [coveralls-image]: https://img.shields.io/coveralls/meituan/monkey.svg?style=flat 45 | [coveralls-url]: https://coveralls.io/r/meituan/monkey?branch=master 46 | -------------------------------------------------------------------------------- /benchmark/benchmark.js: -------------------------------------------------------------------------------- 1 | process.env.DEBUG = 'mock:benchmark'; 2 | 3 | var debug = require('debug')('mock:benchmark'), 4 | Mock = require('..'), 5 | mock = new Mock(__dirname + '/data'); 6 | 7 | debug('benchmark start'); 8 | debug('100 times execution for each test'); 9 | 10 | [ 11 | '/', '/shop/123', '/ktv/check/123', '/ktv/orderinfo/123', 12 | '/ktv/return/fail', '/ktv/orders' 13 | ].forEach(function(uri) { 14 | for (var i=0; i<100; i++) { 15 | mock.get(uri); 16 | } 17 | debug(uri); 18 | }); 19 | -------------------------------------------------------------------------------- /benchmark/data/bankList.json: -------------------------------------------------------------------------------- 1 | { 2 | "bankcardList": [ 3 | { 4 | "id": 4, 5 | "code": "abc", 6 | "name": "中国农业银行", 7 | "type": 0, 8 | "displayflag": 0, 9 | "ioschannel": "", 10 | "androidchannel": "", 11 | "pcichannel": 1, 12 | "orderby": 1, 13 | "paytypeid": 1, 14 | "bankcode": "1005", 15 | "bankname": "中国农业银行", 16 | "bankclass": "abc" 17 | }, 18 | { 19 | "id": 2, 20 | "code": "cmb", 21 | "name": "招商银行", 22 | "type": 0, 23 | "displayflag": 0, 24 | "ioschannel": "", 25 | "androidchannel": "", 26 | "pcichannel": 0, 27 | "orderby": 2, 28 | "paytypeid": 0, 29 | "bankcode": "CMB", 30 | "bankname": "招商银行", 31 | "bankclass": "cmb" 32 | }, 33 | { 34 | "id": 3, 35 | "code": "ccb", 36 | "name": "建设银行", 37 | "type": 0, 38 | "displayflag": 0, 39 | "ioschannel": "", 40 | "androidchannel": "", 41 | "pcichannel": 1, 42 | "orderby": 3, 43 | "paytypeid": 1, 44 | "bankcode": "1034", 45 | "bankname": "建设银行", 46 | "bankclass": "ccb" 47 | }, 48 | { 49 | "id": 5, 50 | "code": "comm", 51 | "name": "交通银行", 52 | "type": 0, 53 | "displayflag": 0, 54 | "ioschannel": "", 55 | "androidchannel": "", 56 | "pcichannel": 0, 57 | "orderby": 4, 58 | "paytypeid": 0, 59 | "bankcode": "COMM", 60 | "bankname": "交通银行", 61 | "bankclass": "boc" 62 | }, 63 | { 64 | "id": 1, 65 | "code": "icbc", 66 | "name": "中国工商银行", 67 | "type": 0, 68 | "displayflag": 0, 69 | "ioschannel": "", 70 | "androidchannel": "", 71 | "pcichannel": 1, 72 | "orderby": 5, 73 | "paytypeid": 1, 74 | "bankcode": "1002", 75 | "bankname": "中国工商银行", 76 | "bankclass": "icbc" 77 | }, 78 | { 79 | "id": 6, 80 | "code": "boc", 81 | "name": "中国银行", 82 | "type": 0, 83 | "displayflag": 0, 84 | "ioschannel": "", 85 | "androidchannel": "", 86 | "pcichannel": 1, 87 | "orderby": 6, 88 | "paytypeid": 1, 89 | "bankcode": "1052", 90 | "bankname": "中国银行", 91 | "bankclass": "bofc" 92 | }, 93 | { 94 | "id": 7, 95 | "code": "cib", 96 | "name": "兴业银行", 97 | "type": 0, 98 | "displayflag": 1, 99 | "ioschannel": "", 100 | "androidchannel": "", 101 | "pcichannel": 1, 102 | "orderby": 7, 103 | "paytypeid": 1, 104 | "bankcode": "1009", 105 | "bankname": "兴业银行", 106 | "bankclass": "cib" 107 | }, 108 | { 109 | "id": 8, 110 | "code": "ceb", 111 | "name": "中国光大银行", 112 | "type": 0, 113 | "displayflag": 1, 114 | "ioschannel": "", 115 | "androidchannel": "", 116 | "pcichannel": 0, 117 | "orderby": 8, 118 | "paytypeid": 0, 119 | "bankcode": "CEBBANK", 120 | "bankname": "中国光大银行", 121 | "bankclass": "cebb" 122 | }, 123 | { 124 | "id": 9, 125 | "code": "spdb", 126 | "name": "上海浦东发展银行", 127 | "type": 0, 128 | "displayflag": 0, 129 | "ioschannel": "", 130 | "androidchannel": "", 131 | "pcichannel": 0, 132 | "orderby": 9, 133 | "paytypeid": 0, 134 | "bankcode": "SPDB", 135 | "bankname": "上海浦东发展银行", 136 | "bankclass": "spdb" 137 | }, 138 | { 139 | "id": 10, 140 | "code": "gdb", 141 | "name": "广东发展银行", 142 | "type": 0, 143 | "displayflag": 0, 144 | "ioschannel": "", 145 | "androidchannel": "", 146 | "pcichannel": 0, 147 | "orderby": 10, 148 | "paytypeid": 0, 149 | "bankcode": "GDB", 150 | "bankname": "广东发展银行", 151 | "bankclass": "gdb" 152 | }, 153 | { 154 | "id": 11, 155 | "code": "citic", 156 | "name": "中信银行", 157 | "type": 0, 158 | "displayflag": 0, 159 | "ioschannel": "", 160 | "androidchannel": "", 161 | "pcichannel": 0, 162 | "orderby": 11, 163 | "paytypeid": 0, 164 | "bankcode": "CITIC", 165 | "bankname": "中信银行", 166 | "bankclass": "zxyh" 167 | }, 168 | { 169 | "id": 12, 170 | "code": "cmbc", 171 | "name": "中国民生银行", 172 | "type": 0, 173 | "displayflag": 0, 174 | "ioschannel": "", 175 | "androidchannel": "", 176 | "pcichannel": 1, 177 | "orderby": 12, 178 | "paytypeid": 1, 179 | "bankcode": "1006", 180 | "bankname": "中国民生银行", 181 | "bankclass": "cmbc" 182 | }, 183 | { 184 | "id": 13, 185 | "code": "pingan", 186 | "name": "平安银行", 187 | "type": 0, 188 | "displayflag": 0, 189 | "ioschannel": "", 190 | "androidchannel": "", 191 | "pcichannel": 0, 192 | "orderby": 13, 193 | "paytypeid": 0, 194 | "bankcode": "SPABANK", 195 | "bankname": "平安银行", 196 | "bankclass": "pingan" 197 | }, 198 | { 199 | "id": 14, 200 | "code": "bjbank", 201 | "name": "北京银行", 202 | "type": 0, 203 | "displayflag": 0, 204 | "ioschannel": "", 205 | "androidchannel": "", 206 | "pcichannel": 1, 207 | "orderby": 14, 208 | "paytypeid": 1, 209 | "bankcode": "1032", 210 | "bankname": "北京银行", 211 | "bankclass": "bob" 212 | }, 213 | { 214 | "id": 15, 215 | "code": "bjrcb", 216 | "name": "北京农商银行", 217 | "type": 0, 218 | "displayflag": 0, 219 | "ioschannel": "", 220 | "androidchannel": "", 221 | "pcichannel": 0, 222 | "orderby": 15, 223 | "paytypeid": 0, 224 | "bankcode": "BJRCB", 225 | "bankname": "北京农商银行", 226 | "bankclass": "bjrcb" 227 | }, 228 | { 229 | "id": 16, 230 | "code": "psbc", 231 | "name": "中国邮政储蓄", 232 | "type": 0, 233 | "displayflag": 0, 234 | "ioschannel": "", 235 | "androidchannel": "", 236 | "pcichannel": 0, 237 | "orderby": 16, 238 | "paytypeid": 0, 239 | "bankcode": "PSBC-DEBIT", 240 | "bankname": "中国邮政储蓄", 241 | "bankclass": "pspc" 242 | }, 243 | { 244 | "id": 17, 245 | "code": "srcb", 246 | "name": "上海农商银行", 247 | "type": 0, 248 | "displayflag": 0, 249 | "ioschannel": "", 250 | "androidchannel": "", 251 | "pcichannel": 0, 252 | "orderby": 17, 253 | "paytypeid": 0, 254 | "bankcode": "SHRCB", 255 | "bankname": "上海农商银行", 256 | "bankclass": "shrcb" 257 | }, 258 | { 259 | "id": 18, 260 | "code": "hzbank", 261 | "name": "杭州银行", 262 | "type": 0, 263 | "displayflag": 0, 264 | "ioschannel": "", 265 | "androidchannel": "", 266 | "pcichannel": 0, 267 | "orderby": 18, 268 | "paytypeid": 0, 269 | "bankcode": "HZCBB2C", 270 | "bankname": "杭州银行", 271 | "bankclass": "hzcb" 272 | } 273 | ] 274 | } 275 | -------------------------------------------------------------------------------- /benchmark/data/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "errmsg": null, 3 | "okmsg": null, 4 | "isLogin": "@BOOLEAN", 5 | "loginUser": { 6 | "userinfo": null, 7 | "userhash": null, 8 | "payhash": null, 9 | "useCache": "@BOOLEAN", 10 | "refreshCache": "@BOOLEAN", 11 | "isLoad": "@BOOLEAN" 12 | }, 13 | "loginUserID": "@ID", 14 | "serviceNumText": "400-660-5335", 15 | "serviceNum": "4006605335", 16 | "hasCookie": "@BOOLEAN", 17 | "noSms": "@BOOLEAN", 18 | "city": { 19 | "id": 1, 20 | "name": "北京", 21 | "slug": "beijing", 22 | "online": 1267632000, 23 | "firstalpha": "bj", 24 | "rank": "S" 25 | }, 26 | "city_info_used_for_front": { 27 | "id": 1, 28 | "name": "北京", 29 | "slug": "beijing", 30 | "online": 1267632000, 31 | "firstalpha": "bj", 32 | "rank": "S" 33 | }, 34 | "isUcweb": "@BOOLEAN", 35 | "isUCHtml5": "@BOOLEAN", 36 | "isQQBrowser": "@BOOLEAN", 37 | "needSmsMO": "@BOOLEAN", 38 | "needNoSms": "@BOOLEAN", 39 | "userdev": "", 40 | "istouch": "@BOOLEAN", 41 | "isFromWeixin": "@BOOLEAN" 42 | } 43 | -------------------------------------------------------------------------------- /benchmark/data/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "seotagConfig": { "fromid": 2 }, 3 | "pageviewData": { 4 | "deals": "13065378,25719247,229318,25719244,374736,25204856,25307732,563199,25204853,665601,24999097,798258,25719234,900896,26644127,1024608,24999091,1078966,25924982,1229674,24999087,1279953,1438563,27055629,5143821,25616343,1642094,24999079,1739192,1841744,24999075,1950687,26645105,2001275,2158348,2210900,26850853,2312435,24999065,2464564,25410571,2517872,2613342,2775621,2822042,2980849,3084610,3124522,3234044,3392611,26798710,3483845,26850829,3597413,3650880,3802489,26301563,3908628,3991767,26747060,4047646,4145665,4264539,4423013,4466100,25924916,4624396,4716236,4782596,4936563,5040655,26439291,5078398,5235714,26336410,5348455,26129848,5396783,25719144,5501465,26104432,5654755,25821235,25719139,5810983,25719136,5913388,26233519,6003448,6116693,25924884,6264154,25924882,6363371,25924879,24998985,6525989,26645015,6634516,27055731,6786092,6839738,26439255,6893621,7096520,7304129,7199163,7251555,7352985,7460170,7608745,26439241,7661617,26336362,7815351,7862214,7972139,25924848,8071007,27056493", 5 | "rs": "out_of_order" 6 | }, 7 | "asyncPageviewData": { 8 | "acms": [ 9 | ] 10 | }, 11 | "dealList": { 12 | "229318": { }, 13 | "374736": { }, 14 | "563199": { }, 15 | "665601": { }, 16 | "798258": { }, 17 | "900896": { }, 18 | "1024608": { }, 19 | "1078966": { }, 20 | "1229674": { }, 21 | "1279953": { }, 22 | "1438563": { }, 23 | "1642094": { }, 24 | "1739192": { }, 25 | "1841744": { }, 26 | "1950687": { }, 27 | "2001275": { }, 28 | "2158348": { }, 29 | "2210900": { }, 30 | "5143821": { }, 31 | "13065378": { }, 32 | "24999075": { }, 33 | "24999079": { }, 34 | "24999087": { }, 35 | "24999091": { }, 36 | "24999097": { }, 37 | "25204853": { }, 38 | "25204856": { }, 39 | "25307732": { }, 40 | "25616343": { }, 41 | "25719234": { }, 42 | "25719244": { }, 43 | "25719247": { }, 44 | "25924982": { }, 45 | "26644127": { }, 46 | "26645105": { }, 47 | "27055629": { } 48 | }, 49 | "dealGeoList": { 50 | "229318": ["东城区", "对外经贸"], 51 | "563199": { 52 | "0": "三里屯", 53 | "1": "中关村", 54 | "2": "西直门/动物园", 55 | "3": "五棵松", 56 | "4": "魏公村", 57 | "5": "三元桥", 58 | "6": "双井", 59 | "7": "西城区", 60 | "8": "酒仙桥", 61 | "10": "紫竹桥", 62 | "11": "望京", 63 | "12": "月坛/金融街", 64 | "13": "亚运村", 65 | "14": "工体", 66 | "15": "朝阳大悦城" 67 | }, 68 | "665601": { 69 | "0": "中关村", 70 | "1": "五道口", 71 | "2": "崇文门", 72 | "3": "新街口", 73 | "4": "北下关", 74 | "5": "回龙观", 75 | "6": "大望路", 76 | "7": "三里屯", 77 | "8": "牡丹园/北太平庄", 78 | "9": "管庄", 79 | "10": "五棵松", 80 | "11": "安贞", 81 | "12": "西城区", 82 | "14": "梨园", 83 | "15": "八里庄", 84 | "16": "亚运村", 85 | "17": "团结湖", 86 | "18": "北京南站/开阳里" 87 | }, 88 | "798258": ["朝阳路杨闸环岛西北角东卫城2号"], 89 | "1279953": ["阜成门", "双井"], 90 | "1438563": ["鼓楼南大街38-2号"], 91 | "1841744": ["国贸", "亚运村", "安贞"], 92 | "2001275": ["和平里东街12号"], 93 | "2210900": ["北河沿大街161号"], 94 | "13065378": ["清河", "回龙观"], 95 | "25924982": ["小屯路10号"] 96 | }, 97 | "categorySlug": "all", 98 | "levelID": "all", 99 | "categoryID": "all", 100 | "categoryName": "全部", 101 | "geoSlug": "all", 102 | "districtID": "all", 103 | "areaID": "all", 104 | "geoType": "all", 105 | "geoName": "全部", 106 | "filter": { 107 | "6": { "type": 6, "name": "电影", "slug": "dianying", "isHot": false, "count": 114 }, 108 | "7": { 109 | "type": 7, 110 | "name": "购物", 111 | "slug": "wanggou", 112 | "isHot": false, 113 | "count": 24, 114 | "sub": { "7": { "type": 7, "name": "全部", "slug": "wanggou", "isHot": false, "count": 0 } } 115 | }, 116 | "9": { 117 | "type": 9, 118 | "name": "旅游", 119 | "slug": "lvyou", 120 | "isHot": false, 121 | "count": 2, 122 | "sub": { "9": { "type": 9, "name": "全部", "slug": "lvyou", "isHot": false, "count": 0 } } 123 | }, 124 | "121": { 125 | "type": 121, 126 | "name": "美食", 127 | "slug": "meishi", 128 | "isHot": false, 129 | "count": 24922, 130 | "sub": { "121": { "type": 121, "name": "全部", "slug": "meishi", "isHot": false, "count": 0 } } 131 | }, 132 | "122": { 133 | "type": 122, 134 | "name": "酒店", 135 | "slug": "jiudian", 136 | "isHot": false, 137 | "count": 8450, 138 | "sub": { "122": { "type": 122, "name": "全部", "slug": "jiudian", "isHot": false, "count": 0 } } 139 | }, 140 | "123": { 141 | "type": 123, 142 | "name": "休闲娱乐", 143 | "slug": "xiuxianyule", 144 | "isHot": false, 145 | "count": 7241, 146 | "sub": { "123": { "type": 123, "name": "全部", "slug": "xiuxianyule", "isHot": false, "count": 0 } } 147 | }, 148 | "124": { 149 | "type": 124, 150 | "name": "生活服务", 151 | "slug": "shenghuo", 152 | "isHot": false, 153 | "count": 3291, 154 | "sub": { "124": { "type": 124, "name": "全部", "slug": "shenghuo", "isHot": false, "count": 0 } } 155 | }, 156 | "125": { 157 | "type": 125, 158 | "name": "丽人", 159 | "slug": "jiankangliren", 160 | "isHot": false, 161 | "count": 4833, 162 | "sub": { "125": { "type": 125, "name": "全部", "slug": "jiankangliren", "isHot": false, "count": 0 } } 163 | }, 164 | "all": { "type": "all", "name": "全部", "slug": "all", "isHot": false, "count": 47779 } 165 | }, 166 | "landmarkFilter": [ ], 167 | "geoFilter": { 168 | "14": { 169 | "id": 14, 170 | "name": "朝阳区", 171 | "slug": "chaoyangqu", 172 | "count": 14693, 173 | "sub": { "all": { "id": 14, "name": "全部", "slug": "chaoyangqu", "count": 14693, "firstchar": "A" } } 174 | }, 175 | "15": { 176 | "id": 15, 177 | "name": "东城区", 178 | "slug": "dongchengqu", 179 | "count": 4909, 180 | "sub": { "all": { "id": 15, "name": "全部", "slug": "dongchengqu", "count": 4909, "firstchar": "A" } } 181 | }, 182 | "16": { 183 | "id": 16, 184 | "name": "西城区", 185 | "slug": "xichengqu", 186 | "count": 4784, 187 | "sub": { "all": { "id": 16, "name": "全部", "slug": "xichengqu", "count": 4784, "firstchar": "A" } } 188 | }, 189 | "17": { 190 | "id": 17, 191 | "name": "海淀区", 192 | "slug": "haidianqu", 193 | "count": 11306, 194 | "sub": { "all": { "id": 17, "name": "全部", "slug": "haidianqu", "count": 11306, "firstchar": "A" } } 195 | }, 196 | "20": { 197 | "id": 20, 198 | "name": "丰台区", 199 | "slug": "fengtaiqu", 200 | "count": 5392, 201 | "sub": { "all": { "id": 20, "name": "全部", "slug": "fengtaiqu", "count": 5392, "firstchar": "A" } } 202 | }, 203 | "172": { 204 | "id": 172, 205 | "name": "石景山区", 206 | "slug": "shijingshanqu", 207 | "count": 1922, 208 | "sub": { "all": { "id": 172, "name": "全部", "slug": "shijingshanqu", "count": 1922, "firstchar": "A" } } 209 | }, 210 | "4750": { 211 | "id": 4750, 212 | "name": "昌平区", 213 | "slug": "changpingqu", 214 | "count": 2950, 215 | "sub": { "all": { "id": 4750, "name": "全部", "slug": "changpingqu", "count": 2950, "firstchar": "A" } } 216 | }, 217 | "4751": { 218 | "id": 4751, 219 | "name": "通州区", 220 | "slug": "tongzhouqu", 221 | "count": 1967, 222 | "sub": { "all": { "id": 4751, "name": "全部", "slug": "tongzhouqu", "count": 1967, "firstchar": "A" } } 223 | }, 224 | "4752": { 225 | "id": 4752, 226 | "name": "大兴区", 227 | "slug": "daxingqu", 228 | "count": 2713, 229 | "sub": { "all": { "id": 4752, "name": "全部", "slug": "daxingqu", "count": 2713, "firstchar": "A" } } 230 | }, 231 | "all": { "id": "all", "name": "全部", "slug": "all", "count": 0 }, 232 | "subway": { 233 | "id": "subway", 234 | "name": "地铁附近", 235 | "slug": "subway", 236 | "count": 30859, 237 | "sub": { 238 | "1": { 239 | "id": 1, 240 | "name": "1号线", 241 | "slug": "yihaoxian", 242 | "count": 6076, 243 | "sub": { "all": { "id": 1, "name": "全部", "slug": "yihaoxian", "count": 6076 } }, 244 | "sort": ["all", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23] 245 | }, 246 | "2": { 247 | "id": 2, 248 | "name": "2号线", 249 | "slug": "erhaoxian", 250 | "count": 5361, 251 | "sub": { "all": { "id": 2, "name": "全部", "slug": "erhaoxian", "count": 5361 } }, 252 | "sort": ["all", 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41] 253 | }, 254 | "3": { 255 | "id": 3, 256 | "name": "4号线", 257 | "slug": "sihaoxian", 258 | "count": 5182, 259 | "sub": { "all": { "id": 3, "name": "全部", "slug": "sihaoxian", "count": 5182 } }, 260 | "sort": [ 261 | "all", 262 | 42, 263 | 43, 264 | 44, 265 | 45, 266 | 46, 267 | 47, 268 | 48, 269 | 49, 270 | 50, 271 | 51, 272 | 52, 273 | 53, 274 | 54, 275 | 55, 276 | 56, 277 | 57, 278 | 58, 279 | 59, 280 | 60, 281 | 61, 282 | 62, 283 | 63, 284 | 64, 285 | 65 286 | ] 287 | }, 288 | "4": { 289 | "id": 4, 290 | "name": "5号线", 291 | "slug": "wuhaoxian", 292 | "count": 5624, 293 | "sub": { "all": { "id": 4, "name": "全部", "slug": "wuhaoxian", "count": 5624 } }, 294 | "sort": ["all", 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88] 295 | }, 296 | "5": { 297 | "id": 5, 298 | "name": "6号线", 299 | "slug": "liuhaoxian", 300 | "count": 6125, 301 | "sub": { "all": { "id": 5, "name": "全部", "slug": "liuhaoxian", "count": 6125 } }, 302 | "sort": [ 303 | "all", 304 | 1248, 305 | 1249, 306 | 1250, 307 | 1251, 308 | 1252, 309 | 1253, 310 | 1254, 311 | 1255, 312 | 1256, 313 | 1257, 314 | 1258, 315 | 1259, 316 | 1260, 317 | 1261, 318 | 1262, 319 | 1263, 320 | 1264, 321 | 1265, 322 | 1266, 323 | 1267 324 | ] 325 | }, 326 | "6": { 327 | "id": 6, 328 | "name": "8号线", 329 | "slug": "bahaoxian", 330 | "count": 1862, 331 | "sub": { "all": { "id": 6, "name": "全部", "slug": "bahaoxian", "count": 1862 } }, 332 | "sort": ["all", 89, 90, 91, 92, 93, 94, 95, 96, 97, 98] 333 | }, 334 | "7": { 335 | "id": 7, 336 | "name": "9号线", 337 | "slug": "jiuhaoxian", 338 | "count": 1435, 339 | "sub": { "all": { "id": 7, "name": "全部", "slug": "jiuhaoxian", "count": 1435 } }, 340 | "sort": ["all", 99, 100, 101, 102, 103, 104, 105, 106, 107] 341 | }, 342 | "8": { 343 | "id": 8, 344 | "name": "10号线", 345 | "slug": "shihaoxian", 346 | "count": 10391, 347 | "sub": { "all": { "id": 8, "name": "全部", "slug": "shihaoxian", "count": 10391 } }, 348 | "sort": [ 349 | "all", 350 | 108, 351 | 109, 352 | 110, 353 | 111, 354 | 112, 355 | 113, 356 | 114, 357 | 115, 358 | 116, 359 | 117, 360 | 118, 361 | 119, 362 | 120, 363 | 121, 364 | 122, 365 | 123, 366 | 124, 367 | 125, 368 | 126, 369 | 127, 370 | 128, 371 | 129, 372 | 1268, 373 | 1269, 374 | 1270, 375 | 1271, 376 | 1272, 377 | 1273, 378 | 1274, 379 | 1275, 380 | 1276, 381 | 1277, 382 | 1278, 383 | 1279, 384 | 1280, 385 | 1281, 386 | 1282, 387 | 1283, 388 | 1284, 389 | 1285, 390 | 1286, 391 | 1287 392 | ] 393 | }, 394 | "9": { 395 | "id": 9, 396 | "name": "13号线", 397 | "slug": "shisanhaoxian", 398 | "count": 4760, 399 | "sub": { "all": { "id": 9, "name": "全部", "slug": "shisanhaoxian", "count": 4760 } }, 400 | "sort": ["all", 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145] 401 | }, 402 | "10": { 403 | "id": 10, 404 | "name": "15号线", 405 | "slug": "shiwuhaoxian", 406 | "count": 1607, 407 | "sub": { "all": { "id": 10, "name": "全部", "slug": "shiwuhaoxian", "count": 1607 } }, 408 | "sort": ["all", 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157] 409 | }, 410 | "11": { 411 | "id": 11, 412 | "name": "亦庄线", 413 | "slug": "yizhuangxian", 414 | "count": 960, 415 | "sub": { "all": { "id": 11, "name": "全部", "slug": "yizhuangxian", "count": 960 } }, 416 | "sort": ["all", 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170] 417 | }, 418 | "12": { 419 | "id": 12, 420 | "name": "八通线", 421 | "slug": "batongxian", 422 | "count": 2193, 423 | "sub": { "all": { "id": 12, "name": "全部", "slug": "batongxian", "count": 2193 } }, 424 | "sort": ["all", 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183] 425 | }, 426 | "13": { 427 | "id": 13, 428 | "name": "大兴线", 429 | "slug": "daxingxian", 430 | "count": 1312, 431 | "sub": { "all": { "id": 13, "name": "全部", "slug": "daxingxian", "count": 1312 } }, 432 | "sort": ["all", 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195] 433 | }, 434 | "14": { 435 | "id": 14, 436 | "name": "房山线", 437 | "slug": "fangshanxian", 438 | "count": 393, 439 | "sub": { "all": { "id": 14, "name": "全部", "slug": "fangshanxian", "count": 393 } }, 440 | "sort": ["all", 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206] 441 | }, 442 | "15": { 443 | "id": 15, 444 | "name": "昌平线", 445 | "slug": "changpingxian", 446 | "count": 255, 447 | "sub": { "all": { "id": 15, "name": "全部", "slug": "changpingxian", "count": 255 } }, 448 | "sort": ["all", 207, 208, 209, 210, 211, 212, 213] 449 | }, 450 | "16": { 451 | "id": 16, 452 | "name": "机场线", 453 | "slug": "jichangxian", 454 | "count": 1306, 455 | "sub": { "all": { "id": 16, "name": "全部", "slug": "jichangxian", "count": 1306 } }, 456 | "sort": ["all", 214, 215, 216, 217] 457 | }, 458 | "all": { "id": "subway", "name": "全部", "slug": "subway", "count": 30859 } 459 | }, 460 | "sort": ["all", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] 461 | } 462 | }, 463 | "attrData": { 464 | "attrFilterList": { 465 | "nnbooking": { "name": "是否需要预约", "type": 3, "ismultiple": 0, "options": { "1": { "name": "免预约" } } }, 466 | "isvoucher": { "name": "是否代金券", "type": 3, "ismultiple": 0, "options": { "1": { "name": "代金券" } } } 467 | }, 468 | "attrFilterCounter": { "nnbooking": { "1": 25350 }, "isvoucher": { "1": 6066 } }, 469 | "attrFilterSelected": [ ] 470 | }, 471 | "sortBy": "default", 472 | "dealSubwayDis": [ ], 473 | "hotMovies": [ ], 474 | "hotTags": [ 475 | { "name": "电影", "slug": "dianying", "isHot": true, "id": 6, "count": 7 }, 476 | { "name": "特色酒店", "slug": "tesejiudian", "isHot": false, "id": 65, "count": 6 }, 477 | { "name": "香锅烤鱼", "slug": "xiangguokaoyu", "isHot": false, "id": 91, "count": 1 }, 478 | { "name": "摄影写真", "slug": "sheying", "isHot": false, "id": 50, "count": 2 }, 479 | { "name": "川湘菜", "slug": "chuancai", "isHot": false, "id": 27, "count": 1 }, 480 | { "name": "电器/数码", "slug": "jiadian", "isHot": false, "id": 75, "count": 1 }, 481 | { "name": "韩国料理", "slug": "hanguoliaoli", "isHot": false, "id": 94, "count": 1 }, 482 | { "name": "甜点饮品", "slug": "tiandianyinpin", "isHot": false, "id": 93, "count": 1 }, 483 | { "name": "美容美体", "slug": "meirong", "isHot": false, "id": 59, "count": 4 }, 484 | { "name": "运动健身", "slug": "jianshen", "isHot": false, "id": 41, "count": 2 }, 485 | { "name": "食品饮料", "slug": "shipinyinliao", "isHot": false, "id": 92, "count": 5 }, 486 | { "name": "鲁菜/北京菜", "slug": "lucaibeijingcai", "isHot": false, "id": 28, "count": 2 }, 487 | { "name": "汽车服务", "slug": "aiche", "isHot": false, "id": 51, "count": 3 }, 488 | { "name": "桌游/电玩", "slug": "zhuoyou", "isHot": false, "id": 43, "count": 1 }, 489 | { "name": "江浙菜", "slug": "jiangzhecai", "isHot": true, "id": 29, "count": 1 }, 490 | { "name": "本地购物", "slug": "bendigouwu", "isHot": false, "id": 82, "count": 3 } 491 | ], 492 | "pageNav": { "totalPage": 200, "nowPage": 1, "itemPerPage": 120, "begin": 1, "end": 7 }, 493 | "navfilter": false, 494 | "rankStrategy": "out_of_order", 495 | "contactCityData": null, 496 | "isDealListTest": true, 497 | "gaCustomVar": { "5": "cate|all|1" } 498 | } 499 | -------------------------------------------------------------------------------- /benchmark/data/ktv/breadNav.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "name": "北京休闲娱乐", "link": "http://example.com/shops/xiuxianyule", "type": "index" }, 3 | { 4 | "name": "海淀区", 5 | "link": "http://example.com/shops/xiuxianyule/haidianqu", 6 | "type": "area/1" 7 | }, 8 | { 9 | "name": "北下关", 10 | "link": "http://example.com/shops/xiuxianyule/beixiaguan", 11 | "type": "area/2" 12 | }, 13 | { 14 | "name": "休闲娱乐", 15 | "link": "http://example.com/shops/xiuxianyule/beixiaguan", 16 | "type": "category/1" 17 | }, 18 | { "name": "KTV", "link": "http://example.com/shops/ktv/beixiaguan", "type": "category/2" }, 19 | { "name": "爆米花量贩式KTV", "link": "/shop/1041138", "type": "category/3" }, 20 | { "name": "在线预订" } 21 | ] 22 | -------------------------------------------------------------------------------- /benchmark/data/ktv/check/:orderid.json: -------------------------------------------------------------------------------- 1 | { 2 | "payTimeLeft": 282681, 3 | "showAlipay": true, 4 | "showTenpay": true, 5 | "showCmpay": false, 6 | "showUpop": true, 7 | "showBank": true, 8 | "payType": "credit_abc", 9 | "orderid": "1418712434935221", 10 | "orderTotal": 153.3, 11 | "breadNav": "atom:///ktv/breadNav.json", 12 | "bankList": "atom:///bankList.json", 13 | "order": "atom:///ktv/order.json" 14 | } 15 | -------------------------------------------------------------------------------- /benchmark/data/ktv/order.json: -------------------------------------------------------------------------------- 1 | { 2 | "orderSkuViews": [ 3 | { 4 | "orderSkuPoiView": { 5 | "name": "爆米花量贩式KTV", 6 | "frontImg": "http://p0.meituan.net/deal/__13246341__8025475.jpg", 7 | "avgScore": 4.4, 8 | "markNumber": 4285 9 | }, 10 | "allowTime": 420, 11 | "latestArrivalTime": "@DATETIME('yyyy-MM-dd HH:mm:ss')", 12 | "latestRefundTime": "@DATETIME('yyyy-MM-dd HH:mm:ss')", 13 | "capacityMin": 6, 14 | "capacityMax": 8, 15 | "roomTypeName": "中房", 16 | "roomId": 2022, 17 | "createTime": "@DATETIME('yyyy-MM-dd HH:mm:ss')", 18 | "endTime": "06:00", 19 | "bookTime": "@DATETIME('yyyy-MM-dd HH:mm:ss')", 20 | "modifyTime": "@DATETIME('yyyy-MM-dd HH:mm:ss')", 21 | "phone": "15201459784", 22 | "arrivalTime": "@DATETIME('HH:mm')", 23 | "roomTypeId": 53, 24 | "poiId": "@ID", 25 | "saleDate": "@DATETIME('yyyy-MM-dd')", 26 | "roomCount": 1, 27 | "allowRefund": 0, 28 | "startTime": "@DATETIME('HH:mm')", 29 | "poiAddress": "西直门北大街28号院7号楼天方饭店旁1-2层", 30 | "poiPhone": "010-62243098/58426666" 31 | } 32 | ], 33 | "createTime": "@DATETIME('yyyy-MM-dd HH:mm:ss')", 34 | "modifyTime": "@DATETIME('yyyy-MM-dd HH:mm:ss')", 35 | "userid": "@ID", 36 | "price": 223.3, 37 | "payDeadline": "@DATETIME('yyyy-MM-dd HH:mm:ss')", 38 | "orderNo": "@ID", 39 | "status|0-10": 0, 40 | "amount": 1 41 | } 42 | -------------------------------------------------------------------------------- /benchmark/data/ktv/orderinfo/:id.json: -------------------------------------------------------------------------------- 1 | { 2 | "orderinfo": "atom:///ktv/order.json" 3 | } 4 | -------------------------------------------------------------------------------- /benchmark/data/ktv/orders.json: -------------------------------------------------------------------------------- 1 | { 2 | "pageNav": { "totalPage": 1, "nowPage": 1, "itemPerPage": 10, "begin": 1, "end": 1 }, 3 | "orders|3-10": ["atom:///ktv/order.json"] 4 | } 5 | -------------------------------------------------------------------------------- /benchmark/data/ktv/return/:type?orderid=:orderid.json: -------------------------------------------------------------------------------- 1 | { 2 | "price": 223.3, 3 | "type|1": ["BOOK_SUCCESS", "BOOK_FAIL", "NO_RESPONSE"], 4 | "breadNav": "atom:///ktv/breadNav.json", 5 | "order": "atom:///ktv/order.json" 6 | } 7 | -------------------------------------------------------------------------------- /benchmark/data/shop/324483.json: -------------------------------------------------------------------------------- 1 | { 2 | "partial": true, 3 | "body": { 4 | "ktvRoomsInfo": { 5 | "2014-12-13": [ ], 6 | "2014-12-14": [ ], 7 | "2014-12-15": [ ], 8 | "2014-12-16": { 9 | "10:00-18:00": [ 10 | { 11 | "roomId": 2731, 12 | "poiId": 1041138, 13 | "roomTypeId": 52, 14 | "saleDate": "2014-12-16", 15 | "startTime": "10:00", 16 | "endTime": "18:00", 17 | "allowTime": 480, 18 | "latestRefundTime": "2014-12-16 15:00:00", 19 | "latestBookTime": "2014-12-16 15:00:00", 20 | "latestArrivalTime": "2014-12-16 15:00:00", 21 | "originPrice": 87.6, 22 | "price": 87.6, 23 | "left": 15, 24 | "roomTypeName": "小房", 25 | "capacityMin": 3, 26 | "capacityMax": 5, 27 | "bookStatus": 0 28 | }, 29 | { 30 | "roomId": 2732, 31 | "poiId": 1041138, 32 | "roomTypeId": 53, 33 | "saleDate": "2014-12-16", 34 | "startTime": "10:00", 35 | "endTime": "18:00", 36 | "allowTime": 480, 37 | "latestRefundTime": "2014-12-16 15:00:00", 38 | "latestBookTime": "2014-12-16 15:00:00", 39 | "latestArrivalTime": "2014-12-16 15:00:00", 40 | "originPrice": 153.3, 41 | "price": 153.3, 42 | "left": 20, 43 | "roomTypeName": "中房", 44 | "capacityMin": 6, 45 | "capacityMax": 8, 46 | "bookStatus": 0 47 | }, 48 | { 49 | "roomId": 2733, 50 | "poiId": 1041138, 51 | "roomTypeId": 54, 52 | "saleDate": "2014-12-16", 53 | "startTime": "10:00", 54 | "endTime": "18:00", 55 | "allowTime": 480, 56 | "latestRefundTime": "2014-12-16 15:00:00", 57 | "latestBookTime": "2014-12-16 15:00:00", 58 | "latestArrivalTime": "2014-12-16 15:00:00", 59 | "originPrice": 219, 60 | "price": 219, 61 | "left": 15, 62 | "roomTypeName": "大房", 63 | "capacityMin": 9, 64 | "capacityMax": 12, 65 | "bookStatus": 0 66 | } 67 | ], 68 | "18:00-次日02:00": [ 69 | { 70 | "roomId": 2734, 71 | "poiId": 1041138, 72 | "roomTypeId": 52, 73 | "saleDate": "2014-12-16", 74 | "startTime": "18:00", 75 | "endTime": "02:00", 76 | "allowTime": 180, 77 | "latestRefundTime": "2014-12-16 18:00:00", 78 | "latestBookTime": "2014-12-16 18:00:00", 79 | "latestArrivalTime": "2014-12-16 19:00:00", 80 | "originPrice": 87.6, 81 | "price": 87.6, 82 | "left": 15, 83 | "roomTypeName": "小房", 84 | "capacityMin": 3, 85 | "capacityMax": 5, 86 | "bookStatus": 0 87 | }, 88 | { 89 | "roomId": 2735, 90 | "poiId": 1041138, 91 | "roomTypeId": 53, 92 | "saleDate": "2014-12-16", 93 | "startTime": "18:00", 94 | "endTime": "02:00", 95 | "allowTime": 180, 96 | "latestRefundTime": "2014-12-16 18:00:00", 97 | "latestBookTime": "2014-12-16 18:00:00", 98 | "latestArrivalTime": "2014-12-16 19:00:00", 99 | "originPrice": 153.3, 100 | "price": 153.3, 101 | "left": 20, 102 | "roomTypeName": "中房", 103 | "capacityMin": 6, 104 | "capacityMax": 8, 105 | "bookStatus": 0 106 | }, 107 | { 108 | "roomId": 2736, 109 | "poiId": 1041138, 110 | "roomTypeId": 54, 111 | "saleDate": "2014-12-16", 112 | "startTime": "18:00", 113 | "endTime": "02:00", 114 | "allowTime": 180, 115 | "latestRefundTime": "2014-12-16 18:00:00", 116 | "latestBookTime": "2014-12-16 18:00:00", 117 | "latestArrivalTime": "2014-12-16 19:00:00", 118 | "originPrice": 219, 119 | "price": 219, 120 | "left": 15, 121 | "roomTypeName": "大房", 122 | "capacityMin": 9, 123 | "capacityMax": 12, 124 | "bookStatus": 0 125 | } 126 | ], 127 | "23:00-次日06:00": [ 128 | { 129 | "roomId": 2737, 130 | "poiId": 1041138, 131 | "roomTypeId": 52, 132 | "saleDate": "2014-12-16", 133 | "startTime": "23:00", 134 | "endTime": "06:00", 135 | "allowTime": 420, 136 | "latestRefundTime": "2014-12-16 23:00:00", 137 | "latestBookTime": "2014-12-16 23:00:00", 138 | "latestArrivalTime": "2014-12-17 00:00:00", 139 | "originPrice": 127.6, 140 | "price": 127.6, 141 | "left": 15, 142 | "roomTypeName": "小房", 143 | "capacityMin": 3, 144 | "capacityMax": 5, 145 | "bookStatus": 0 146 | }, 147 | { 148 | "roomId": 2738, 149 | "poiId": 1041138, 150 | "roomTypeId": 53, 151 | "saleDate": "2014-12-16", 152 | "startTime": "23:00", 153 | "endTime": "06:00", 154 | "allowTime": 420, 155 | "latestRefundTime": "2014-12-16 23:00:00", 156 | "latestBookTime": "2014-12-16 23:00:00", 157 | "latestArrivalTime": "2014-12-17 00:00:00", 158 | "originPrice": 223.3, 159 | "price": 223.3, 160 | "left": 20, 161 | "roomTypeName": "中房", 162 | "capacityMin": 6, 163 | "capacityMax": 8, 164 | "bookStatus": 0 165 | }, 166 | { 167 | "roomId": 2739, 168 | "poiId": 1041138, 169 | "roomTypeId": 54, 170 | "saleDate": "2014-12-16", 171 | "startTime": "23:00", 172 | "endTime": "06:00", 173 | "allowTime": 420, 174 | "latestRefundTime": "2014-12-16 23:00:00", 175 | "latestBookTime": "2014-12-16 23:00:00", 176 | "latestArrivalTime": "2014-12-17 00:00:00", 177 | "originPrice": 319, 178 | "price": 319, 179 | "left": 15, 180 | "roomTypeName": "大房", 181 | "capacityMin": 9, 182 | "capacityMax": 12, 183 | "bookStatus": 0 184 | } 185 | ] 186 | }, 187 | "2014-12-17": { 188 | "10:00-18:00": [ 189 | { 190 | "roomId": 2740, 191 | "poiId": 1041138, 192 | "roomTypeId": 52, 193 | "saleDate": "2014-12-17", 194 | "startTime": "10:00", 195 | "endTime": "18:00", 196 | "allowTime": 480, 197 | "latestRefundTime": "2014-12-17 15:00:00", 198 | "latestBookTime": "2014-12-17 15:00:00", 199 | "latestArrivalTime": "2014-12-17 15:00:00", 200 | "originPrice": 87.6, 201 | "price": 87.6, 202 | "left": 15, 203 | "roomTypeName": "小房", 204 | "capacityMin": 3, 205 | "capacityMax": 5, 206 | "bookStatus": 0 207 | }, 208 | { 209 | "roomId": 2741, 210 | "poiId": 1041138, 211 | "roomTypeId": 53, 212 | "saleDate": "2014-12-17", 213 | "startTime": "10:00", 214 | "endTime": "18:00", 215 | "allowTime": 480, 216 | "latestRefundTime": "2014-12-17 15:00:00", 217 | "latestBookTime": "2014-12-17 15:00:00", 218 | "latestArrivalTime": "2014-12-17 15:00:00", 219 | "originPrice": 153.3, 220 | "price": 153.3, 221 | "left": 20, 222 | "roomTypeName": "中房", 223 | "capacityMin": 6, 224 | "capacityMax": 8, 225 | "bookStatus": 0 226 | }, 227 | { 228 | "roomId": 2742, 229 | "poiId": 1041138, 230 | "roomTypeId": 54, 231 | "saleDate": "2014-12-17", 232 | "startTime": "10:00", 233 | "endTime": "18:00", 234 | "allowTime": 480, 235 | "latestRefundTime": "2014-12-17 15:00:00", 236 | "latestBookTime": "2014-12-17 15:00:00", 237 | "latestArrivalTime": "2014-12-17 15:00:00", 238 | "originPrice": 219, 239 | "price": 219, 240 | "left": 15, 241 | "roomTypeName": "大房", 242 | "capacityMin": 9, 243 | "capacityMax": 12, 244 | "bookStatus": 0 245 | } 246 | ], 247 | "18:00-次日02:00": [ 248 | { 249 | "roomId": 2743, 250 | "poiId": 1041138, 251 | "roomTypeId": 52, 252 | "saleDate": "2014-12-17", 253 | "startTime": "18:00", 254 | "endTime": "02:00", 255 | "allowTime": 180, 256 | "latestRefundTime": "2014-12-17 18:00:00", 257 | "latestBookTime": "2014-12-17 18:00:00", 258 | "latestArrivalTime": "2014-12-17 19:00:00", 259 | "originPrice": 87.6, 260 | "price": 87.6, 261 | "left": 15, 262 | "roomTypeName": "小房", 263 | "capacityMin": 3, 264 | "capacityMax": 5, 265 | "bookStatus": 0 266 | }, 267 | { 268 | "roomId": 2744, 269 | "poiId": 1041138, 270 | "roomTypeId": 53, 271 | "saleDate": "2014-12-17", 272 | "startTime": "18:00", 273 | "endTime": "02:00", 274 | "allowTime": 180, 275 | "latestRefundTime": "2014-12-17 18:00:00", 276 | "latestBookTime": "2014-12-17 18:00:00", 277 | "latestArrivalTime": "2014-12-17 19:00:00", 278 | "originPrice": 153.3, 279 | "price": 153.3, 280 | "left": 20, 281 | "roomTypeName": "中房", 282 | "capacityMin": 6, 283 | "capacityMax": 8, 284 | "bookStatus": 0 285 | }, 286 | { 287 | "roomId": 2745, 288 | "poiId": 1041138, 289 | "roomTypeId": 54, 290 | "saleDate": "2014-12-17", 291 | "startTime": "18:00", 292 | "endTime": "02:00", 293 | "allowTime": 180, 294 | "latestRefundTime": "2014-12-17 18:00:00", 295 | "latestBookTime": "2014-12-17 18:00:00", 296 | "latestArrivalTime": "2014-12-17 19:00:00", 297 | "originPrice": 219, 298 | "price": 219, 299 | "left": 15, 300 | "roomTypeName": "大房", 301 | "capacityMin": 9, 302 | "capacityMax": 12, 303 | "bookStatus": 0 304 | } 305 | ], 306 | "23:00-次日06:00": [ 307 | { 308 | "roomId": 2746, 309 | "poiId": 1041138, 310 | "roomTypeId": 52, 311 | "saleDate": "2014-12-17", 312 | "startTime": "23:00", 313 | "endTime": "06:00", 314 | "allowTime": 420, 315 | "latestRefundTime": "2014-12-17 23:00:00", 316 | "latestBookTime": "2014-12-17 23:00:00", 317 | "latestArrivalTime": "2014-12-18 00:00:00", 318 | "originPrice": 127.6, 319 | "price": 127.6, 320 | "left": 15, 321 | "roomTypeName": "小房", 322 | "capacityMin": 3, 323 | "capacityMax": 5, 324 | "bookStatus": 0 325 | }, 326 | { 327 | "roomId": 2747, 328 | "poiId": 1041138, 329 | "roomTypeId": 53, 330 | "saleDate": "2014-12-17", 331 | "startTime": "23:00", 332 | "endTime": "06:00", 333 | "allowTime": 420, 334 | "latestRefundTime": "2014-12-17 23:00:00", 335 | "latestBookTime": "2014-12-17 23:00:00", 336 | "latestArrivalTime": "2014-12-18 00:00:00", 337 | "originPrice": 223.3, 338 | "price": 223.3, 339 | "left": 20, 340 | "roomTypeName": "中房", 341 | "capacityMin": 6, 342 | "capacityMax": 8, 343 | "bookStatus": 0 344 | }, 345 | { 346 | "roomId": 2748, 347 | "poiId": 1041138, 348 | "roomTypeId": 54, 349 | "saleDate": "2014-12-17", 350 | "startTime": "23:00", 351 | "endTime": "06:00", 352 | "allowTime": 420, 353 | "latestRefundTime": "2014-12-17 23:00:00", 354 | "latestBookTime": "2014-12-17 23:00:00", 355 | "latestArrivalTime": "2014-12-18 00:00:00", 356 | "originPrice": 319, 357 | "price": 319, 358 | "left": 15, 359 | "roomTypeName": "大房", 360 | "capacityMin": 9, 361 | "capacityMax": 12, 362 | "bookStatus": 0 363 | } 364 | ] 365 | }, 366 | "2014-12-18": { 367 | "10:00-18:00": [ 368 | { 369 | "roomId": 2749, 370 | "poiId": 1041138, 371 | "roomTypeId": 52, 372 | "saleDate": "2014-12-18", 373 | "startTime": "10:00", 374 | "endTime": "18:00", 375 | "allowTime": 480, 376 | "latestRefundTime": "2014-12-18 15:00:00", 377 | "latestBookTime": "2014-12-18 15:00:00", 378 | "latestArrivalTime": "2014-12-18 15:00:00", 379 | "originPrice": 87.6, 380 | "price": 87.6, 381 | "left": 15, 382 | "roomTypeName": "小房", 383 | "capacityMin": 3, 384 | "capacityMax": 5, 385 | "bookStatus": 0 386 | }, 387 | { 388 | "roomId": 2750, 389 | "poiId": 1041138, 390 | "roomTypeId": 53, 391 | "saleDate": "2014-12-18", 392 | "startTime": "10:00", 393 | "endTime": "18:00", 394 | "allowTime": 480, 395 | "latestRefundTime": "2014-12-18 15:00:00", 396 | "latestBookTime": "2014-12-18 15:00:00", 397 | "latestArrivalTime": "2014-12-18 15:00:00", 398 | "originPrice": 153.3, 399 | "price": 153.3, 400 | "left": 20, 401 | "roomTypeName": "中房", 402 | "capacityMin": 6, 403 | "capacityMax": 8, 404 | "bookStatus": 0 405 | }, 406 | { 407 | "roomId": 2751, 408 | "poiId": 1041138, 409 | "roomTypeId": 54, 410 | "saleDate": "2014-12-18", 411 | "startTime": "10:00", 412 | "endTime": "18:00", 413 | "allowTime": 480, 414 | "latestRefundTime": "2014-12-18 15:00:00", 415 | "latestBookTime": "2014-12-18 15:00:00", 416 | "latestArrivalTime": "2014-12-18 15:00:00", 417 | "originPrice": 219, 418 | "price": 219, 419 | "left": 15, 420 | "roomTypeName": "大房", 421 | "capacityMin": 9, 422 | "capacityMax": 12, 423 | "bookStatus": 0 424 | } 425 | ], 426 | "18:00-次日02:00": [ 427 | { 428 | "roomId": 2752, 429 | "poiId": 1041138, 430 | "roomTypeId": 52, 431 | "saleDate": "2014-12-18", 432 | "startTime": "18:00", 433 | "endTime": "02:00", 434 | "allowTime": 180, 435 | "latestRefundTime": "2014-12-18 18:00:00", 436 | "latestBookTime": "2014-12-18 18:00:00", 437 | "latestArrivalTime": "2014-12-18 19:00:00", 438 | "originPrice": 87.6, 439 | "price": 87.6, 440 | "left": 15, 441 | "roomTypeName": "小房", 442 | "capacityMin": 3, 443 | "capacityMax": 5, 444 | "bookStatus": 0 445 | }, 446 | { 447 | "roomId": 2753, 448 | "poiId": 1041138, 449 | "roomTypeId": 53, 450 | "saleDate": "2014-12-18", 451 | "startTime": "18:00", 452 | "endTime": "02:00", 453 | "allowTime": 180, 454 | "latestRefundTime": "2014-12-18 18:00:00", 455 | "latestBookTime": "2014-12-18 18:00:00", 456 | "latestArrivalTime": "2014-12-18 19:00:00", 457 | "originPrice": 153.3, 458 | "price": 153.3, 459 | "left": 20, 460 | "roomTypeName": "中房", 461 | "capacityMin": 6, 462 | "capacityMax": 8, 463 | "bookStatus": 0 464 | }, 465 | { 466 | "roomId": 2754, 467 | "poiId": 1041138, 468 | "roomTypeId": 54, 469 | "saleDate": "2014-12-18", 470 | "startTime": "18:00", 471 | "endTime": "02:00", 472 | "allowTime": 180, 473 | "latestRefundTime": "2014-12-18 18:00:00", 474 | "latestBookTime": "2014-12-18 18:00:00", 475 | "latestArrivalTime": "2014-12-18 19:00:00", 476 | "originPrice": 219, 477 | "price": 219, 478 | "left": 15, 479 | "roomTypeName": "大房", 480 | "capacityMin": 9, 481 | "capacityMax": 12, 482 | "bookStatus": 0 483 | } 484 | ], 485 | "23:00-次日06:00": [ 486 | { 487 | "roomId": 2755, 488 | "poiId": 1041138, 489 | "roomTypeId": 52, 490 | "saleDate": "2014-12-18", 491 | "startTime": "23:00", 492 | "endTime": "06:00", 493 | "allowTime": 420, 494 | "latestRefundTime": "2014-12-18 23:00:00", 495 | "latestBookTime": "2014-12-18 23:00:00", 496 | "latestArrivalTime": "2014-12-19 00:00:00", 497 | "originPrice": 127.6, 498 | "price": 127.6, 499 | "left": 15, 500 | "roomTypeName": "小房", 501 | "capacityMin": 3, 502 | "capacityMax": 5, 503 | "bookStatus": 0 504 | }, 505 | { 506 | "roomId": 2756, 507 | "poiId": 1041138, 508 | "roomTypeId": 53, 509 | "saleDate": "2014-12-18", 510 | "startTime": "23:00", 511 | "endTime": "06:00", 512 | "allowTime": 420, 513 | "latestRefundTime": "2014-12-18 23:00:00", 514 | "latestBookTime": "2014-12-18 23:00:00", 515 | "latestArrivalTime": "2014-12-19 00:00:00", 516 | "originPrice": 223.3, 517 | "price": 223.3, 518 | "left": 20, 519 | "roomTypeName": "中房", 520 | "capacityMin": 6, 521 | "capacityMax": 8, 522 | "bookStatus": 0 523 | }, 524 | { 525 | "roomId": 2757, 526 | "poiId": 1041138, 527 | "roomTypeId": 54, 528 | "saleDate": "2014-12-18", 529 | "startTime": "23:00", 530 | "endTime": "06:00", 531 | "allowTime": 420, 532 | "latestRefundTime": "2014-12-18 23:00:00", 533 | "latestBookTime": "2014-12-18 23:00:00", 534 | "latestArrivalTime": "2014-12-19 00:00:00", 535 | "originPrice": 319, 536 | "price": 319, 537 | "left": 15, 538 | "roomTypeName": "大房", 539 | "capacityMin": 9, 540 | "capacityMax": 12, 541 | "bookStatus": 0 542 | } 543 | ] 544 | }, 545 | "2014-12-19": { 546 | "10:00-18:00": [ 547 | { 548 | "roomId": 2758, 549 | "poiId": 1041138, 550 | "roomTypeId": 52, 551 | "saleDate": "2014-12-19", 552 | "startTime": "10:00", 553 | "endTime": "18:00", 554 | "allowTime": 480, 555 | "latestRefundTime": "2014-12-19 15:00:00", 556 | "latestBookTime": "2014-12-19 15:00:00", 557 | "latestArrivalTime": "2014-12-19 15:00:00", 558 | "originPrice": 87.6, 559 | "price": 87.6, 560 | "left": 15, 561 | "roomTypeName": "小房", 562 | "capacityMin": 3, 563 | "capacityMax": 5, 564 | "bookStatus": 0 565 | }, 566 | { 567 | "roomId": 2759, 568 | "poiId": 1041138, 569 | "roomTypeId": 53, 570 | "saleDate": "2014-12-19", 571 | "startTime": "10:00", 572 | "endTime": "18:00", 573 | "allowTime": 480, 574 | "latestRefundTime": "2014-12-19 15:00:00", 575 | "latestBookTime": "2014-12-19 15:00:00", 576 | "latestArrivalTime": "2014-12-19 15:00:00", 577 | "originPrice": 153.3, 578 | "price": 153.3, 579 | "left": 20, 580 | "roomTypeName": "中房", 581 | "capacityMin": 6, 582 | "capacityMax": 8, 583 | "bookStatus": 0 584 | }, 585 | { 586 | "roomId": 2760, 587 | "poiId": 1041138, 588 | "roomTypeId": 54, 589 | "saleDate": "2014-12-19", 590 | "startTime": "10:00", 591 | "endTime": "18:00", 592 | "allowTime": 480, 593 | "latestRefundTime": "2014-12-19 15:00:00", 594 | "latestBookTime": "2014-12-19 15:00:00", 595 | "latestArrivalTime": "2014-12-19 15:00:00", 596 | "originPrice": 219, 597 | "price": 219, 598 | "left": 15, 599 | "roomTypeName": "大房", 600 | "capacityMin": 9, 601 | "capacityMax": 12, 602 | "bookStatus": 0 603 | } 604 | ], 605 | "18:00-次日02:00": [ 606 | { 607 | "roomId": 2761, 608 | "poiId": 1041138, 609 | "roomTypeId": 52, 610 | "saleDate": "2014-12-19", 611 | "startTime": "18:00", 612 | "endTime": "02:00", 613 | "allowTime": 120, 614 | "latestRefundTime": "2014-12-19 18:00:00", 615 | "latestBookTime": "2014-12-19 18:00:00", 616 | "latestArrivalTime": "2014-12-19 19:00:00", 617 | "originPrice": 87.6, 618 | "price": 87.6, 619 | "left": 10, 620 | "roomTypeName": "小房", 621 | "capacityMin": 3, 622 | "capacityMax": 5, 623 | "bookStatus": 0 624 | }, 625 | { 626 | "roomId": 2762, 627 | "poiId": 1041138, 628 | "roomTypeId": 53, 629 | "saleDate": "2014-12-19", 630 | "startTime": "18:00", 631 | "endTime": "02:00", 632 | "allowTime": 120, 633 | "latestRefundTime": "2014-12-19 18:00:00", 634 | "latestBookTime": "2014-12-19 18:00:00", 635 | "latestArrivalTime": "2014-12-19 19:00:00", 636 | "originPrice": 153.3, 637 | "price": 153.3, 638 | "left": 10, 639 | "roomTypeName": "中房", 640 | "capacityMin": 6, 641 | "capacityMax": 8, 642 | "bookStatus": 0 643 | }, 644 | { 645 | "roomId": 2763, 646 | "poiId": 1041138, 647 | "roomTypeId": 54, 648 | "saleDate": "2014-12-19", 649 | "startTime": "18:00", 650 | "endTime": "02:00", 651 | "allowTime": 120, 652 | "latestRefundTime": "2014-12-19 18:00:00", 653 | "latestBookTime": "2014-12-19 18:00:00", 654 | "latestArrivalTime": "2014-12-19 19:00:00", 655 | "originPrice": 219, 656 | "price": 219, 657 | "left": 10, 658 | "roomTypeName": "大房", 659 | "capacityMin": 9, 660 | "capacityMax": 12, 661 | "bookStatus": 0 662 | } 663 | ], 664 | "23:00-次日06:00": [ 665 | { 666 | "roomId": 2764, 667 | "poiId": 1041138, 668 | "roomTypeId": 52, 669 | "saleDate": "2014-12-19", 670 | "startTime": "23:00", 671 | "endTime": "06:00", 672 | "allowTime": 420, 673 | "latestRefundTime": "2014-12-19 23:00:00", 674 | "latestBookTime": "2014-12-19 23:00:00", 675 | "latestArrivalTime": "2014-12-20 00:00:00", 676 | "originPrice": 127.6, 677 | "price": 127.6, 678 | "left": 15, 679 | "roomTypeName": "小房", 680 | "capacityMin": 3, 681 | "capacityMax": 5, 682 | "bookStatus": 0 683 | }, 684 | { 685 | "roomId": 2765, 686 | "poiId": 1041138, 687 | "roomTypeId": 53, 688 | "saleDate": "2014-12-19", 689 | "startTime": "23:00", 690 | "endTime": "06:00", 691 | "allowTime": 420, 692 | "latestRefundTime": "2014-12-19 23:00:00", 693 | "latestBookTime": "2014-12-19 23:00:00", 694 | "latestArrivalTime": "2014-12-20 00:00:00", 695 | "originPrice": 223.3, 696 | "price": 223.3, 697 | "left": 20, 698 | "roomTypeName": "中房", 699 | "capacityMin": 6, 700 | "capacityMax": 8, 701 | "bookStatus": 0 702 | }, 703 | { 704 | "roomId": 2766, 705 | "poiId": 1041138, 706 | "roomTypeId": 54, 707 | "saleDate": "2014-12-19", 708 | "startTime": "23:00", 709 | "endTime": "06:00", 710 | "allowTime": 420, 711 | "latestRefundTime": "2014-12-19 23:00:00", 712 | "latestBookTime": "2014-12-19 23:00:00", 713 | "latestArrivalTime": "2014-12-20 00:00:00", 714 | "originPrice": 319, 715 | "price": 319, 716 | "left": 15, 717 | "roomTypeName": "大房", 718 | "capacityMin": 9, 719 | "capacityMax": 12, 720 | "bookStatus": 0 721 | } 722 | ] 723 | } 724 | } 725 | } 726 | } 727 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | // Simple cli stub for monkey 4 | 5 | var Mock = require('..'); 6 | var argv = process.argv; 7 | 8 | if (argv.length < 4) { 9 | console.log('Usage: ./mock '); 10 | process.exit(1); 11 | } 12 | 13 | var mock = new Mock(argv[2]); 14 | console.log(JSON.stringify(mock.get(JSON.parse(argv[3])))); 15 | -------------------------------------------------------------------------------- /docs/Home.md: -------------------------------------------------------------------------------- 1 | ## Get Started 2 | 3 | ### Terminology 4 | 5 | * `monkey`: Data simulate system 6 | * `stub`: Frontend of monkey 7 | * `mock`: Backend of monkey 8 | * `request`: Normalized HTTP request, see docs in `mock-request2` 9 | * `response`: Simulate data, see docs in `mock-response` 10 | * `mapping`: Matching rule between request and response, see docs in `mock-locator` 11 | 12 | `monkey` is a data simulate system consist of `stub` and `mock`. 13 | 14 | `stub` is 15 | 16 | * framework relevant 17 | * the frontend of `monkey` 18 | * stub framework information such as HTTP request 19 | * transform it appropriately 20 | * send it back to `mock`. 21 | 22 | `mock` 23 | 24 | * consume predefined HTTP request 25 | * searching for matching simulate data in filesystem 26 | * send it back to `stub` 27 | 28 | ![mindmap](./assets/mindmap.png) 29 | 30 | 31 | ### Example 32 | 33 | Below is a example of use `mock` to find simulate data match uri `/deal/123456`. 34 | 35 | var Mock = require('monkeyjs'); 36 | var mock = new Mock('./data-dir'); 37 | var data = mock.get({ 38 | "uri": "/deal/123456", 39 | "method": "POST", 40 | }); 41 | 42 | `data` will be the content of file `./data-dir/deal/123456.POST.json` if it exists. 43 | 44 | ### API 45 | 46 | #### `Mock(string: mount)` 47 | Initialize a `mock` object with simulate data directory `mount`. 48 | 49 | #### `mock#get(Object: request)` 50 | Find simulate data match `request` 51 | 52 | #### `mock#getByRPC(string: funcName, Array: args)` 53 | Find simulate data match RPC call: function name `funcName` with arguments `args`. 54 | 55 | As a matter of fact, `mock` transform RPC call to `/funcName?0=args[0]&1=args[1]...`, before locate matching simulate data. 56 | 57 | 58 | ### Stub 59 | 60 | `stub` make `monkey` possible to adapt to every system, even with different programing languages. 61 | 62 | #### stub-cli 63 | For example, below is the simplest `stub`(`stub-cli`) comes with `mock` 64 | 65 | ```js 66 | #! /usr/bin/env node 67 | 68 | // Simple cli stub for monkey 69 | 70 | var Mock = require('..'); 71 | var argv = process.argv; 72 | 73 | if (argv.length < 4) { 74 | console.log('Usage: ./mock '); 75 | process.exit(1); 76 | } 77 | 78 | var mock = new Mock(argv[2]); 79 | console.log(JSON.stringify(mock.get(JSON.parse(argv[3])))); 80 | ``` 81 | 82 | To use `stub-cli` inside a PHP framework, 83 | 84 | ```php 85 | $response = shell_exec('./node_modules/.bin/mock ./data-dir ' . escapeshellarg(json_encode($request))); 86 | ``` 87 | 88 | It is the PHP framework's decision of how and where to use `mock`, as long as it construct `$request` properly. 89 | 90 | `stub-cli` take the `mock-data-dir` and `http-request` argument, transform them, ask `mock` for simulate data and return(`log`) the `response` to the invoker. 91 | 92 | #### stub-http 93 | `stub-http` is a stand alone http server. 94 | 95 | It accept normalized `request`, use `mock` to find simulate data and return the `response`. 96 | 97 | Below is the key source code of `stub-http` 98 | 99 | ```js 100 | // Usage ./stub-http [-p port] [-a address] 101 | 102 | var mock = new Mock(mount); 103 | 104 | createServer(mock) 105 | .listen(8080, 'localhost'); 106 | 107 | function createServer(mock) { 108 | return http.createServer(function(req, res) { 109 | var chunks = []; 110 | I 111 | req.on('data', function(chunk) { 112 | }); 113 | 114 | req.on('end', function(chunk) { 115 | if (chunk) { chunks.push(chunk); } 116 | 117 | var status = 200, content, type = 'application/json'; 118 | try { 119 | var request = Buffer.concat(chunks).toString(); 120 | 121 | var response = mock.get(request); 122 | 123 | if (!response) { 124 | status = 404; 125 | } 126 | content = JSON.stringify(response); 127 | 128 | } catch (err) { 129 | content = err.stack; 130 | type = 'text/plain'; 131 | 132 | } finally { 133 | res.writeHead(status, { 134 | 'Content-Length': Buffer.byteLength(content, 'utf8'), 135 | 'Content-Type': type, 136 | }); 137 | res.end(content); 138 | } 139 | }); 140 | }); 141 | } 142 | ``` 143 | 144 | To start `stub-http`, you must specify a simulate data directory for `mock` to searching for simulate data. 145 | 146 | After accept a `request`, `http-stub` passing it to `mock.get`. `mock` will searching for available simulate data and return it. before send `response` back to client, `stub-http` check if the response is valid(if not, tell the client what's going on). 147 | 148 | Example client code with PHP is following 149 | 150 | ```php 151 | http_post_data('http://localhost:8080', array( 152 | uri: '/deal/123456', 153 | method: 'POST' 154 | )); 155 | ``` 156 | 157 | `stub-proxy` is a similar `stub` to `stub-http`. 158 | -------------------------------------------------------------------------------- /docs/assets/mindmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meituan/monkey/55905e3a5bfff355e8f9f8327332edfbede57de3/docs/assets/mindmap.png -------------------------------------------------------------------------------- /githooks.json: -------------------------------------------------------------------------------- 1 | { 2 | "pre-commit": { 3 | "github.com/git-hooks/contrib": [ 4 | "jshint", 5 | "bashlint" 6 | ] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /githooks/pre-commit/test: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | npm run test-cov 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var util = require('util'); 4 | var qs = require('querystring'); 5 | var debug = require('debug')('mock:mock'); 6 | var _ = require('lodash'); 7 | var Locator = require('mock-locator'); 8 | var Request = require('mock-request2'); 9 | var Response = require('mock-response'); 10 | 11 | /** 12 | * Initialize a `Mock` instance. 13 | * 14 | * @constructor 15 | * @param {string} mount Root directory of mock data 16 | * @param {Object} options 17 | * @param {boolean} options.params Whether to supply response with additional 18 | * request and response params, default to true 19 | */ 20 | function Mock(mount, options) { 21 | // convert to absolute path 22 | this.mount = mount; 23 | this.options = options || {}; 24 | this.options.params = this.options.params === undefined ? true : this.options.params; 25 | this.locator = new Locator(mount); 26 | 27 | debug('mount at %s', this.mount); 28 | } 29 | 30 | /** 31 | * Get simulate data by HTTP request. 32 | * 33 | * @param {Object|string} req HTTP request 34 | * @return {Response|null} Simulate data, null if no match found 35 | * 36 | * Example: 37 | * 38 | * res = { 39 | * 'status': 200, 40 | * //'some other extra meta data': 'blanblan', 41 | * 'body': { 42 | * 'hello': 'world' 43 | * // other extra simulate data 44 | * } 45 | * }; 46 | */ 47 | Mock.prototype.get = function(req) { 48 | debug('request', req); 49 | var res, match; 50 | 51 | req = new Request(req); 52 | debug('parsed request', req); 53 | 54 | match = this.locator.find(req); 55 | debug('match', match); 56 | if (!match) { 57 | return null; 58 | } 59 | 60 | if (this.options.params) { 61 | res = new Response(match.path, { 62 | mount: this.mount, 63 | overrides: req.params, 64 | params: match.params, 65 | }); 66 | } else { 67 | res = new Response(match.path, { 68 | mount: this.mount, 69 | }); 70 | } 71 | debug('response', res); 72 | 73 | return res; 74 | }; 75 | 76 | /** 77 | * Get multiple simulate once. 78 | * @param {Array|Object} reqs Array or hash of requests. 79 | * @return {Array|Object} 80 | */ 81 | Mock.prototype.mget = function(reqs) { 82 | if (_.isArray(reqs)) { 83 | return reqs.map(function(req) { 84 | return this.get(req); 85 | }, this); 86 | } 87 | 88 | var ress = {}; 89 | _.forOwn(reqs, function(req, key) { 90 | ress[key] = this.get(req); 91 | }, this); 92 | return ress; 93 | }; 94 | 95 | /** 96 | * Get simulate data by function call. 97 | * 98 | * Usage: 99 | * 100 | * mock.getByRPC('funcName', ['hello', 2]) 101 | * 102 | * Will match response file 103 | * 104 | * /funcName?0=hello&1=2 105 | * 106 | * @param {string|Array.} funcName 107 | * @param {Array=} args 108 | */ 109 | Mock.prototype.getByRPC = function(funcName, args) { 110 | if (_.isArray(funcName)) { 111 | funcName = funcName.join('/'); 112 | } 113 | args = args || []; 114 | 115 | return this.get(util.format('/%s?%s', funcName, qs.stringify(args))); 116 | }; 117 | 118 | /** 119 | * Export simulate data to file system. 120 | * @param {Object|string} req HTTP request 121 | * @param {string} type Simulate data type, eg json, html etc 122 | * @param {string} content Simulate data 123 | */ 124 | Mock.prototype.set = function(req, type, content) { 125 | req = new Request(req); 126 | req.pathname.reduce(function(prefix, segment, index) { 127 | prefix = path.join(prefix, segment); 128 | 129 | if (index === req.pathname.length - 1) { 130 | prefix = prefix + '.' + req.method.toUpperCase() + '.' + type.toLowerCase(); 131 | fs.writeFileSync(prefix, content, 'utf8'); 132 | return; 133 | } 134 | 135 | if (!fs.existsSync(prefix)) { 136 | fs.mkdirSync(prefix); 137 | } 138 | 139 | return prefix; 140 | }, this.mount); 141 | }; 142 | 143 | module.exports = Mock; 144 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monkeyjs", 3 | "version": "3.6.1", 4 | "description": "Monkey backend: Data mapping system", 5 | "main": "index.js", 6 | "scripts": { 7 | "debug": "./node_modules/.bin/mocha --debug-brk --require should test", 8 | "test": "./node_modules/.bin/mocha --require should test", 9 | "test-cov": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test", 10 | "test-travis": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should", 11 | "benchmark": "node ./benchmark/benchmark.js" 12 | }, 13 | "bin": { 14 | "mock": "bin/cli.js" 15 | }, 16 | "files": [ 17 | "bin/", 18 | "index.js" 19 | ], 20 | "repository": "meituan/monkey", 21 | "keywords": [ 22 | "mock" 23 | ], 24 | "author": "zhongchiyu", 25 | "license": "MIT", 26 | "devDependencies": { 27 | "istanbul": "^0.3.5", 28 | "mocha": "^2.0.1", 29 | "should": "^4.3.1", 30 | "temp": "^0.8.1" 31 | }, 32 | "dependencies": { 33 | "mock-locator": "^3.0.0", 34 | "mock-request2": "^1.0.0", 35 | "mock-response": "^3.2.1", 36 | "debug": "^2.1.0", 37 | "handlebars": "^2.0.0", 38 | "lodash": "^2.4.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/fixtures/params/:hi.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/rpc.json: -------------------------------------------------------------------------------- 1 | { 2 | "body": { 3 | "child": "child" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world" 3 | } 4 | -------------------------------------------------------------------------------- /test/mock.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var temp = require('temp'); 4 | var Mock = require('..'); 5 | 6 | describe('Mock', function(){ 7 | var mount = './test/fixtures', 8 | mock; 9 | 10 | beforeEach(function() { 11 | mock = new Mock(mount); 12 | }); 13 | 14 | describe('get', function() { 15 | it('should fetch simulate data', function() { 16 | var res = mock.get('/simple'); 17 | res.body.should.eql({'hello': 'world'}); 18 | }); 19 | 20 | it('should allow disable params', function() { 21 | var res = mock.get('/params/something'); 22 | res.body.should.eql({ 23 | 'hi': 'something', 24 | 'hello': 'world', 25 | }); 26 | 27 | mock = new Mock(mount, {params: false}); 28 | res = mock.get('/params/something'); 29 | res.body.should.eql({ 30 | 'hello': 'world', 31 | }); 32 | }); 33 | }); 34 | 35 | describe('mget', function() { 36 | it('should handle array of requests', function() { 37 | var ress = mock.mget(['/simple', '/rpc']); 38 | ress.should.eql([ 39 | {'body': {'hello': 'world'}, 'status': 200}, 40 | {'body': {'child': 'child'}}, 41 | ]); 42 | }); 43 | 44 | it('should handle hash of requests', function() { 45 | var ress = mock.mget({ 46 | a: '/simple', 47 | b: '/rpc', 48 | }); 49 | ress.should.eql({ 50 | a: {'body': {'hello': 'world'}, 'status': 200}, 51 | b: {'body': {'child': 'child'}}, 52 | }); 53 | }); 54 | }); 55 | 56 | describe('getByRPC', function() { 57 | it('should allow member to be string', function() { 58 | var res = mock.getByRPC('rpc', [1, 2]); 59 | res.body.should.eql({ 60 | 'child': 'child' 61 | }); 62 | }); 63 | 64 | it('should support array as function name', function() { 65 | var res = mock.getByRPC(['rpc']); 66 | res.body.should.eql({ 67 | 'child': 'child' 68 | }); 69 | }); 70 | }); 71 | 72 | describe('set', function() { 73 | var mount = temp.mkdirSync('mock-set'); 74 | var mock = new Mock(mount); 75 | 76 | it('should create parent directories', function() { 77 | var req = {uri: '/path/to/ajax', method: 'post'}; 78 | var content = JSON.stringify({hello: 'world'}); 79 | mock.set(req, 'json', content); 80 | var location = path.join(mount, 'path', 'to', 'ajax.POST.json'); 81 | fs.existsSync(location).should.be.ok; 82 | fs.readFileSync(location, 'utf8').should.eql(content); 83 | }); 84 | }); 85 | }); 86 | --------------------------------------------------------------------------------