vulcan 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (260) hide show
  1. data/lib/vulcan/cli.rb +20 -10
  2. data/lib/vulcan/version.rb +1 -1
  3. data/server/Procfile +1 -1
  4. data/server/bin/web +10 -0
  5. data/server/lib/cloudant.coffee +20 -0
  6. data/server/lib/logger.coffee +17 -0
  7. data/server/lib/on.coffee +10 -0
  8. data/server/lib/spawner.coffee +65 -0
  9. data/server/node_modules/coffee-script/CNAME +1 -0
  10. data/server/node_modules/coffee-script/LICENSE +22 -0
  11. data/server/node_modules/coffee-script/README +51 -0
  12. data/server/node_modules/coffee-script/Rakefile +78 -0
  13. data/server/node_modules/coffee-script/bin/cake +7 -0
  14. data/server/node_modules/coffee-script/bin/coffee +7 -0
  15. data/server/node_modules/coffee-script/extras/jsl.conf +44 -0
  16. data/server/node_modules/coffee-script/lib/coffee-script/browser.js +92 -0
  17. data/server/node_modules/coffee-script/lib/coffee-script/cake.js +111 -0
  18. data/server/node_modules/coffee-script/lib/coffee-script/coffee-script.js +167 -0
  19. data/server/node_modules/coffee-script/lib/coffee-script/command.js +500 -0
  20. data/server/node_modules/coffee-script/lib/coffee-script/grammar.js +606 -0
  21. data/server/node_modules/coffee-script/lib/coffee-script/helpers.js +77 -0
  22. data/server/node_modules/coffee-script/lib/coffee-script/index.js +11 -0
  23. data/server/node_modules/coffee-script/lib/coffee-script/lexer.js +788 -0
  24. data/server/node_modules/coffee-script/lib/coffee-script/nodes.js +2986 -0
  25. data/server/node_modules/coffee-script/lib/coffee-script/optparse.js +138 -0
  26. data/server/node_modules/coffee-script/lib/coffee-script/parser.js +683 -0
  27. data/server/node_modules/coffee-script/lib/coffee-script/repl.js +261 -0
  28. data/server/node_modules/coffee-script/lib/coffee-script/rewriter.js +349 -0
  29. data/server/node_modules/coffee-script/lib/coffee-script/scope.js +146 -0
  30. data/server/node_modules/coffee-script/package.json +55 -0
  31. data/server/node_modules/connect-form/History.md +0 -6
  32. data/server/node_modules/connect-form/lib/connect-form.js +2 -4
  33. data/server/node_modules/connect-form/node_modules/formidable/Readme.md +64 -36
  34. data/server/node_modules/connect-form/node_modules/formidable/lib/incoming_form.js +5 -1
  35. data/server/node_modules/connect-form/node_modules/formidable/package.json +20 -6
  36. data/server/node_modules/connect-form/node_modules/formidable/test/common.js +5 -6
  37. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/file/funkyfilename.txt +1 -0
  38. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/js/special-chars-in-filename.js +1 -1
  39. data/server/node_modules/connect-form/node_modules/formidable/test/{slow → integration}/test-fixtures.js +38 -33
  40. data/server/node_modules/connect-form/node_modules/formidable/test/legacy/simple/test-incoming-form.js +11 -0
  41. data/server/node_modules/connect-form/node_modules/formidable/test/run.js +1 -6
  42. data/server/node_modules/connect-form/node_modules/formidable/test/unit/test-incoming-form.js +63 -0
  43. data/server/node_modules/connect-form/package.json +27 -5
  44. data/server/node_modules/cradle/README.md +10 -10
  45. data/server/node_modules/cradle/lib/cradle.js +117 -523
  46. data/server/node_modules/cradle/lib/cradle/database/attachments.js +120 -0
  47. data/server/node_modules/cradle/lib/cradle/database/changes.js +56 -0
  48. data/server/node_modules/cradle/lib/cradle/database/documents.js +215 -0
  49. data/server/node_modules/cradle/lib/cradle/database/index.js +65 -0
  50. data/server/node_modules/cradle/lib/cradle/database/views.js +125 -0
  51. data/server/node_modules/cradle/node_modules/follow/LICENSE +202 -0
  52. data/server/node_modules/cradle/node_modules/follow/README.md +164 -0
  53. data/server/node_modules/cradle/node_modules/follow/Rakefile +54 -0
  54. data/server/node_modules/cradle/node_modules/follow/api.js +35 -0
  55. data/server/node_modules/cradle/node_modules/follow/browser/eventemitter2.js +453 -0
  56. data/server/node_modules/cradle/node_modules/follow/browser/export.js +78 -0
  57. data/server/node_modules/cradle/node_modules/follow/browser/index.html +14 -0
  58. data/server/node_modules/cradle/node_modules/follow/browser/jquery-1.6.1.min.js +18 -0
  59. data/server/node_modules/cradle/node_modules/follow/browser/log4js.js +46 -0
  60. data/server/node_modules/cradle/node_modules/follow/browser/main.js +92 -0
  61. data/server/node_modules/cradle/node_modules/follow/browser/querystring.js +28 -0
  62. data/server/node_modules/cradle/node_modules/follow/browser/request.jquery.js +237 -0
  63. data/server/node_modules/cradle/node_modules/follow/browser/require.js +33 -0
  64. data/server/node_modules/cradle/node_modules/follow/browser/util.js +28 -0
  65. data/server/node_modules/cradle/node_modules/follow/cli.js +101 -0
  66. data/server/node_modules/cradle/node_modules/follow/lib/feed.js +556 -0
  67. data/server/node_modules/cradle/node_modules/follow/lib/index.js +66 -0
  68. data/server/node_modules/cradle/node_modules/follow/lib/stream.js +305 -0
  69. data/server/node_modules/cradle/node_modules/follow/node_modules/request/LICENSE +55 -0
  70. data/server/node_modules/cradle/node_modules/follow/node_modules/request/README.md +285 -0
  71. data/server/node_modules/cradle/node_modules/follow/node_modules/request/main.js +618 -0
  72. data/server/node_modules/cradle/node_modules/follow/node_modules/request/mimetypes.js +146 -0
  73. data/server/node_modules/cradle/node_modules/follow/node_modules/request/oauth.js +34 -0
  74. data/server/node_modules/cradle/node_modules/follow/node_modules/request/package.json +42 -0
  75. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/googledoodle.png +0 -0
  76. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/run.sh +6 -0
  77. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/server.js +57 -0
  78. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-body.js +90 -0
  79. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-cookie.js +29 -0
  80. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-cookiejar.js +90 -0
  81. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-errors.js +30 -0
  82. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-oauth.js +109 -0
  83. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-pipes.js +167 -0
  84. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-proxy.js +39 -0
  85. data/server/node_modules/cradle/node_modules/follow/node_modules/request/tests/test-timeout.js +87 -0
  86. data/server/node_modules/cradle/node_modules/follow/node_modules/request/uuid.js +19 -0
  87. data/server/node_modules/cradle/node_modules/follow/node_modules/request/vendor/cookie/index.js +55 -0
  88. data/server/node_modules/cradle/node_modules/follow/node_modules/request/vendor/cookie/jar.js +72 -0
  89. data/server/node_modules/cradle/node_modules/follow/package.json +45 -0
  90. data/server/node_modules/cradle/node_modules/follow/test/couch.js +153 -0
  91. data/server/node_modules/cradle/node_modules/follow/test/follow.js +136 -0
  92. data/server/node_modules/cradle/node_modules/follow/test/issues.js +178 -0
  93. data/server/node_modules/cradle/node_modules/follow/test/issues/10.js +24 -0
  94. data/server/node_modules/cradle/node_modules/follow/test/stream.js +493 -0
  95. data/server/node_modules/cradle/node_modules/request/LICENSE +55 -0
  96. data/server/node_modules/cradle/node_modules/request/README.md +287 -0
  97. data/server/node_modules/cradle/node_modules/request/forever.js +103 -0
  98. data/server/node_modules/cradle/node_modules/request/main.js +913 -0
  99. data/server/node_modules/cradle/node_modules/request/mimetypes.js +152 -0
  100. data/server/node_modules/cradle/node_modules/request/oauth.js +34 -0
  101. data/server/node_modules/cradle/node_modules/request/package.json +42 -0
  102. data/server/node_modules/cradle/node_modules/request/tests/googledoodle.png +0 -0
  103. data/server/node_modules/cradle/node_modules/request/tests/run.js +38 -0
  104. data/server/node_modules/cradle/node_modules/request/tests/server.js +82 -0
  105. data/server/node_modules/cradle/node_modules/request/tests/squid.conf +77 -0
  106. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.cnf +20 -0
  107. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.crl +0 -0
  108. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.crt +17 -0
  109. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.csr +13 -0
  110. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.key +18 -0
  111. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/ca.srl +1 -0
  112. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.cnf +19 -0
  113. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.crt +16 -0
  114. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.csr +11 -0
  115. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.js +28 -0
  116. data/server/node_modules/cradle/node_modules/request/tests/ssl/ca/server.key +9 -0
  117. data/server/node_modules/cradle/node_modules/request/tests/ssl/npm-ca.crt +16 -0
  118. data/server/node_modules/cradle/node_modules/request/tests/ssl/test.crt +15 -0
  119. data/server/node_modules/cradle/node_modules/request/tests/ssl/test.key +15 -0
  120. data/server/node_modules/cradle/node_modules/request/tests/test-body.js +95 -0
  121. data/server/node_modules/cradle/node_modules/request/tests/test-cookie.js +29 -0
  122. data/server/node_modules/cradle/node_modules/request/tests/test-cookiejar.js +90 -0
  123. data/server/node_modules/cradle/node_modules/request/tests/test-defaults.js +68 -0
  124. data/server/node_modules/cradle/node_modules/request/tests/test-errors.js +37 -0
  125. data/server/node_modules/cradle/node_modules/request/tests/test-headers.js +52 -0
  126. data/server/node_modules/cradle/node_modules/request/tests/test-httpModule.js +94 -0
  127. data/server/node_modules/cradle/node_modules/request/tests/test-https-strict.js +97 -0
  128. data/server/node_modules/cradle/node_modules/request/tests/test-https.js +86 -0
  129. data/server/node_modules/cradle/node_modules/request/tests/test-oauth.js +117 -0
  130. data/server/node_modules/cradle/node_modules/request/tests/test-params.js +92 -0
  131. data/server/node_modules/cradle/node_modules/request/tests/test-pipes.js +202 -0
  132. data/server/node_modules/cradle/node_modules/request/tests/test-proxy.js +39 -0
  133. data/server/node_modules/cradle/node_modules/request/tests/test-qs.js +28 -0
  134. data/server/node_modules/cradle/node_modules/request/tests/test-redirect.js +154 -0
  135. data/server/node_modules/cradle/node_modules/request/tests/test-timeout.js +87 -0
  136. data/server/node_modules/cradle/node_modules/request/tests/test-toJSON.js +14 -0
  137. data/server/node_modules/cradle/node_modules/request/tests/test-tunnel.js +61 -0
  138. data/server/node_modules/cradle/node_modules/request/tunnel.js +229 -0
  139. data/server/node_modules/cradle/node_modules/request/uuid.js +19 -0
  140. data/server/node_modules/cradle/node_modules/request/vendor/cookie/index.js +65 -0
  141. data/server/node_modules/cradle/node_modules/request/vendor/cookie/jar.js +72 -0
  142. data/server/node_modules/cradle/node_modules/vargs/package.json +33 -10
  143. data/server/node_modules/cradle/package.json +50 -12
  144. data/server/node_modules/cradle/test/cache-test.js +1 -4
  145. data/server/node_modules/cradle/test/connection-test.js +179 -0
  146. data/server/node_modules/cradle/test/database-attachment-test.js +344 -0
  147. data/server/node_modules/cradle/test/database-cache-test.js +132 -0
  148. data/server/node_modules/cradle/test/database-test.js +219 -0
  149. data/server/node_modules/cradle/test/database-view-test.js +141 -0
  150. data/server/node_modules/cradle/test/fixtures/databases.json +28 -1
  151. data/server/node_modules/cradle/test/helpers/seed.js +14 -5
  152. data/server/node_modules/cradle/test/response-test.js +1 -1
  153. data/server/node_modules/express/History.md +16 -0
  154. data/server/node_modules/express/bin/express +7 -6
  155. data/server/node_modules/express/lib-cov/application.js +510 -0
  156. data/server/node_modules/express/lib-cov/express.js +65 -0
  157. data/server/node_modules/express/lib-cov/middleware.js +54 -0
  158. data/server/node_modules/express/lib-cov/request.js +225 -0
  159. data/server/node_modules/express/lib-cov/response.js +611 -0
  160. data/server/node_modules/express/lib-cov/router/collection.js +40 -0
  161. data/server/node_modules/express/lib-cov/router/index.js +515 -0
  162. data/server/node_modules/express/lib-cov/router/methods.js +9 -0
  163. data/server/node_modules/express/lib-cov/router/route.js +68 -0
  164. data/server/node_modules/express/lib-cov/utils.js +151 -0
  165. data/server/node_modules/express/lib-cov/view.js +81 -0
  166. data/server/node_modules/express/lib/express.js +1 -1
  167. data/server/node_modules/express/lib/http.js +1 -2
  168. data/server/node_modules/express/lib/request.js +2 -2
  169. data/server/node_modules/express/lib/router/methods.js +10 -1
  170. data/server/node_modules/express/node_modules/connect/lib/connect.js +1 -1
  171. data/server/node_modules/express/node_modules/connect/lib/http.js +3 -2
  172. data/server/node_modules/express/node_modules/connect/lib/middleware/limit.js +0 -2
  173. data/server/node_modules/express/node_modules/connect/lib/middleware/session.js +1 -2
  174. data/server/node_modules/express/node_modules/connect/node_modules/formidable/Readme.md +42 -25
  175. data/server/node_modules/express/node_modules/connect/node_modules/formidable/lib/incoming_form.js +1 -0
  176. data/server/node_modules/express/node_modules/connect/node_modules/formidable/package.json +14 -3
  177. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/legacy/simple/test-incoming-form.js +11 -0
  178. data/server/node_modules/express/node_modules/connect/package.json +35 -7
  179. data/server/node_modules/express/node_modules/mime/package.json +30 -8
  180. data/server/node_modules/express/node_modules/mkdirp/README.markdown +35 -2
  181. data/server/node_modules/express/node_modules/mkdirp/examples/pow.js +1 -1
  182. data/server/node_modules/express/node_modules/mkdirp/index.js +72 -13
  183. data/server/node_modules/express/node_modules/mkdirp/package.json +39 -21
  184. data/server/node_modules/express/node_modules/mkdirp/test/chmod.js +38 -0
  185. data/server/node_modules/express/node_modules/mkdirp/test/clobber.js +37 -0
  186. data/server/node_modules/express/node_modules/mkdirp/test/perm.js +32 -0
  187. data/server/node_modules/express/node_modules/mkdirp/test/perm_sync.js +39 -0
  188. data/server/node_modules/express/node_modules/mkdirp/test/sync.js +27 -0
  189. data/server/node_modules/express/node_modules/mkdirp/test/umask.js +28 -0
  190. data/server/node_modules/express/node_modules/mkdirp/test/umask_sync.js +27 -0
  191. data/server/node_modules/express/node_modules/qs/History.md +10 -0
  192. data/server/node_modules/express/node_modules/qs/Readme.md +9 -2
  193. data/server/node_modules/express/node_modules/qs/examples.js +3 -0
  194. data/server/node_modules/express/node_modules/qs/lib/querystring.js +8 -6
  195. data/server/node_modules/express/node_modules/qs/package.json +26 -8
  196. data/server/node_modules/express/node_modules/qs/test/parse.js +13 -1
  197. data/server/node_modules/express/node_modules/qs/test/stringify.js +45 -37
  198. data/server/node_modules/express/package.json +55 -16
  199. data/server/node_modules/express/test.js +41 -0
  200. data/server/node_modules/knox/package.json +26 -4
  201. data/server/node_modules/node-uuid/package.json +40 -11
  202. data/server/node_modules/node-uuid/test/test.js +1 -1
  203. data/server/node_modules/nodemon/README.md +120 -0
  204. data/server/node_modules/nodemon/nodemon.js +518 -0
  205. data/server/node_modules/nodemon/nodemonignore.example +11 -0
  206. data/server/node_modules/nodemon/package.json +49 -0
  207. data/server/node_modules/restler/README.md +144 -94
  208. data/server/node_modules/restler/lib/multipartform.js +2 -0
  209. data/server/node_modules/restler/lib/restler.js +218 -61
  210. data/server/node_modules/restler/package.json +35 -8
  211. data/server/node_modules/restler/test/all.js +6 -1
  212. data/server/node_modules/restler/test/restler.js +624 -118
  213. data/server/package.json +14 -10
  214. data/server/web.coffee +64 -0
  215. data/server/web.js +15 -3
  216. metadata +170 -57
  217. data/server/index.js +0 -14
  218. data/server/node_modules/connect-form/LICENSE +0 -22
  219. data/server/node_modules/connect-form/index.js +0 -100
  220. data/server/node_modules/connect-form/node_modules/formidable/test/fast/test-incoming-form.js +0 -45
  221. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/no-filename/generic.http +0 -13
  222. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-chrome-13.http +0 -26
  223. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http +0 -24
  224. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-safari-5.http +0 -23
  225. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-chrome-12.http +0 -24
  226. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-7.http +0 -22
  227. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-8.http +0 -22
  228. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-safari-5.http +0 -22
  229. data/server/node_modules/connect-form/node_modules/formidable/test/fixture/multi_video.upload +0 -0
  230. data/server/node_modules/cradle/Makefile +0 -10
  231. data/server/node_modules/cradle/test/cradle-test.js +0 -650
  232. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/no-filename/generic.http +0 -13
  233. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-chrome-13.http +0 -26
  234. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-firefox-3.6.http +0 -24
  235. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/osx-safari-5.http +0 -23
  236. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-chrome-12.http +0 -24
  237. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-7.http +0 -22
  238. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-ie-8.http +0 -22
  239. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/http/special-chars-in-filename/xp-safari-5.http +0 -22
  240. data/server/node_modules/express/node_modules/connect/node_modules/formidable/test/fixture/multi_video.upload +0 -0
  241. data/server/node_modules/express/node_modules/connect/test.js +0 -52
  242. data/server/node_modules/express/testing/foo/app.js +0 -35
  243. data/server/node_modules/express/testing/foo/package.json +0 -9
  244. data/server/node_modules/express/testing/foo/public/stylesheets/style.css +0 -8
  245. data/server/node_modules/express/testing/foo/routes/index.js +0 -10
  246. data/server/node_modules/express/testing/foo/views/index.jade +0 -2
  247. data/server/node_modules/express/testing/foo/views/layout.jade +0 -6
  248. data/server/node_modules/express/testing/index.js +0 -43
  249. data/server/node_modules/express/testing/public/test.txt +0 -2971
  250. data/server/node_modules/express/testing/views/page.html +0 -1
  251. data/server/node_modules/express/testing/views/page.jade +0 -3
  252. data/server/node_modules/express/testing/views/test.md +0 -1
  253. data/server/node_modules/express/testing/views/user/index.jade +0 -1
  254. data/server/node_modules/express/testing/views/user/list.jade +0 -1
  255. data/server/node_modules/knox/lib/knox/mime/index.js +0 -308
  256. data/server/node_modules/knox/lib/knox/mime/test.js +0 -59
  257. data/server/node_modules/node-uuid/test/benchmark-native +0 -0
  258. data/server/node_modules/on/index.js +0 -13
  259. data/server/node_modules/restler/test/test_helper.js +0 -163
  260. data/server/node_modules/spawner/index.js +0 -106
@@ -0,0 +1,178 @@
1
+ var tap = require('tap')
2
+ , test = tap.test
3
+ , util = require('util')
4
+ , request = require('request')
5
+ , traceback = require('traceback')
6
+
7
+ var lib = require('../lib')
8
+ , couch = require('./couch')
9
+ , follow = require('../api')
10
+
11
+ couch.setup(test)
12
+
13
+ test('Issue #5', function(t) {
14
+ var saw = { loops:0, seqs:{} }
15
+
16
+ var saw_change = false
17
+ // -2 means I want to see the last change.
18
+ var feed = follow({'db':couch.DB, since:-2}, function(er, change) {
19
+ t.equal(change.seq, 3, 'Got the latest change, 3')
20
+ t.false(saw_change, 'Only one callback run for since=-2 (assuming no subsequent change')
21
+ saw_change = true
22
+
23
+ process.nextTick(function() { feed.stop() })
24
+ feed.on('stop', function() {
25
+ // Test using since=-1 (AKA since="now").
26
+ follow({'db':couch.DB, since:'now'}, function(er, change) {
27
+ t.equal(change.seq, 4, 'Only get changes made after starting the feed')
28
+ t.equal(change.id, "You're in now, now", 'Got the subsequent change')
29
+
30
+ this.stop()
31
+ t.end()
32
+ })
33
+
34
+ // Let that follower settle in, then send it something
35
+ setTimeout(function() {
36
+ var doc = { _id:"You're in now, now", movie:"Spaceballs" }
37
+ request.post({uri:couch.DB, json:doc}, function(er) {
38
+ if(er) throw er
39
+ })
40
+ }, couch.rtt())
41
+ })
42
+ })
43
+ })
44
+
45
+ couch.setup(test) // Back to the expected documents
46
+
47
+ test('Issue #6', function(t) {
48
+ // When we see change 1, delete the database. The rest should still come in, then the error indicating deletion.
49
+ var saw = { seqs:{}, redid:false, redo_err:null }
50
+
51
+ follow(couch.DB, function(er, change) {
52
+ if(!er) {
53
+ saw.seqs[change.seq] = true
54
+ t.notOk(change.last_seq, 'Change '+change.seq+' ha no .last_seq')
55
+ if(change.seq == 1) {
56
+ couch.redo(function(er) {
57
+ saw.redid = true
58
+ saw.redo_err = er
59
+ })
60
+ }
61
+ }
62
+
63
+ else setTimeout(function() {
64
+ // Give the redo time to finish, then confirm that everything happened as expected.
65
+ // Hopefully this error indicates the database was deleted.
66
+ t.ok(er.message.match(/deleted .* 3$/), 'Got delete error after change 3')
67
+ t.ok(er.deleted, 'Error object indicates database deletion')
68
+ t.equal(er.last_seq, 3, 'Error object indicates the last change number')
69
+
70
+ t.ok(saw.seqs[1], 'Change 1 was processed')
71
+ t.ok(saw.seqs[2], 'Change 2 was processed')
72
+ t.ok(saw.seqs[3], 'Change 3 was processed')
73
+ t.ok(saw.redid, 'The redo function ran')
74
+ t.false(saw.redo_err, 'No problem redoing the database')
75
+
76
+ return t.end()
77
+ }, couch.rtt() * 2)
78
+ })
79
+ })
80
+
81
+ test('Issue #8', function(t) {
82
+ var timeouts = timeout_tracker()
83
+
84
+ // Detect inappropriate timeouts after the run.
85
+ var runs = {'set':false, 'clear':false}
86
+ function badSetT() {
87
+ runs.set = true
88
+ return setTimeout.apply(this, arguments)
89
+ }
90
+
91
+ function badClearT() {
92
+ runs.clear = true
93
+ return clearTimeout.apply(this, arguments)
94
+ }
95
+
96
+ follow(couch.DB, function(er, change) {
97
+ t.false(er, 'Got a feed')
98
+ t.equal(change.seq, 1, 'Handler only runs for one change')
99
+
100
+ this.on('stop', check_timeouts)
101
+ this.stop()
102
+
103
+ function check_timeouts() {
104
+ t.equal(timeouts().length, 0, 'No timeouts by the time stop fires')
105
+
106
+ lib.timeouts(badSetT, badClearT)
107
+
108
+ // And give it a moment to try something bad.
109
+ setTimeout(final_timeout_check, 250)
110
+ function final_timeout_check() {
111
+ t.equal(timeouts().length, 0, 'No lingering timeouts after teardown: ' + tims(timeouts()))
112
+ t.false(runs.set, 'No more setTimeouts ran')
113
+ t.false(runs.clear, 'No more clearTimeouts ran')
114
+
115
+ t.end()
116
+ }
117
+ }
118
+ })
119
+ })
120
+
121
+ test('Issue #9', function(t) {
122
+ var timeouts = timeout_tracker()
123
+
124
+ follow({db:couch.DB, inactivity_ms:30000}, function(er, change) {
125
+ if(change.seq == 1)
126
+ return // Let it run through once, just for fun.
127
+
128
+ t.equal(change.seq, 2, 'The second change will be the last')
129
+ this.stop()
130
+
131
+ setTimeout(check_inactivity_timer, 250)
132
+ function check_inactivity_timer() {
133
+ t.equal(timeouts().length, 0, 'No lingering timeouts after teardown: ' + tims(timeouts()))
134
+ timeouts().forEach(function(id) { clearTimeout(id) })
135
+ t.end()
136
+ }
137
+ })
138
+ })
139
+
140
+ //
141
+ // Utilities
142
+ //
143
+
144
+ function timeout_tracker() {
145
+ // Return an array tracking in-flight timeouts.
146
+ var timeouts = []
147
+ var set_num = 0
148
+
149
+ lib.timeouts(set, clear)
150
+ return function() { return timeouts }
151
+
152
+ function set() {
153
+ var result = setTimeout.apply(this, arguments)
154
+
155
+ var caller = traceback()[2]
156
+ set_num += 1
157
+ result.caller = '('+set_num+') ' + (caller.method || caller.name || '<a>') + ' in ' + caller.file + ':' + caller.line
158
+ //console.error('setTimeout: ' + result.caller)
159
+
160
+ timeouts.push(result)
161
+ //console.error('inflight ('+timeouts.length+'): ' + tims(timeouts))
162
+ return result
163
+ }
164
+
165
+ function clear(id) {
166
+ //var caller = traceback()[2]
167
+ //caller = (caller.method || caller.name || '<a>') + ' in ' + caller.file + ':' + caller.line
168
+ //console.error('clearTimeout: ' + (id && id.caller) + ' <- ' + caller)
169
+
170
+ timeouts = timeouts.filter(function(element) { return element !== id })
171
+ //console.error('inflight ('+timeouts.length+'): ' + tims(timeouts))
172
+ return clearTimeout.apply(this, arguments)
173
+ }
174
+ }
175
+
176
+ function tims(arr) {
177
+ return JSON.stringify(arr.map(function(timer) { return timer.caller }))
178
+ }
@@ -0,0 +1,24 @@
1
+ var tap = require('tap')
2
+ , test = tap.test
3
+ , util = require('util')
4
+
5
+ // Issue #10 is about missing log4js. This file sets the environment variable to disable it.
6
+ process.env.log_plain = true
7
+
8
+ var lib = require('../../lib')
9
+ , couch = require('../couch')
10
+ , follow = require('../../api')
11
+
12
+ couch.setup(test)
13
+
14
+ test('Issue #10', function(t) {
15
+ follow({db:couch.DB, inactivity_ms:30000}, function(er, change) {
16
+ console.error('Change: ' + JSON.stringify(change))
17
+ if(change.seq == 2)
18
+ this.stop()
19
+
20
+ this.on('stop', function() {
21
+ t.end()
22
+ })
23
+ })
24
+ })
@@ -0,0 +1,493 @@
1
+ var tap = require('tap')
2
+ , test = tap.test
3
+ , util = require('util')
4
+ , request = require('request')
5
+
6
+ var couch = require('./couch')
7
+ , follow = require('../api')
8
+
9
+
10
+ couch.setup(test)
11
+
12
+ test('The Changes stream API', function(t) {
13
+ var feed = new follow.Changes
14
+
15
+ t.type(feed.statusCode, 'null', 'Changes has a .statusCode (initially null)')
16
+ t.type(feed.setHeader, 'function', 'Changes has a .setHeader() method')
17
+ t.type(feed.headers, 'object', 'Changes has a .headers object')
18
+ t.same(feed.headers, {}, 'Changes headers are initially empty')
19
+
20
+ t.end()
21
+ })
22
+
23
+ test('Readable Stream API', function(t) {
24
+ var feed = new follow.Changes
25
+
26
+ t.is(feed.readable, true, 'Changes is a readable stream')
27
+
28
+ t.type(feed.setEncoding, 'function', 'Changes has .setEncoding() method')
29
+ t.type(feed.pause, 'function', 'Changes has .pause() method')
30
+ t.type(feed.resume, 'function', 'Changes has .resume() method')
31
+ t.type(feed.destroy, 'function', 'Changes has .destroy() method')
32
+ t.type(feed.destroySoon, 'function', 'Changes has .destroySoon() method')
33
+ t.type(feed.pipe, 'function', 'Changes has .pipe() method')
34
+
35
+ t.end()
36
+ })
37
+
38
+ test('Writatable Stream API', function(t) {
39
+ var feed = new follow.Changes
40
+
41
+ t.is(feed.writable, true, 'Changes is a writable stream')
42
+
43
+ t.type(feed.write, 'function', 'Changes has .write() method')
44
+ t.type(feed.end, 'function', 'Changes has .end() method')
45
+ t.type(feed.destroy, 'function', 'Changes has .destroy() method')
46
+ t.type(feed.destroySoon, 'function', 'Changes has .destroySoon() method')
47
+
48
+ t.end()
49
+ })
50
+
51
+ test('Error conditions', function(t) {
52
+ var feed = new follow.Changes
53
+ t.throws(write, 'Throw if the feed type is not defined')
54
+
55
+ feed = new follow.Changes
56
+ feed.feed = 'neither longpoll nor continuous'
57
+ t.throws(write, 'Throw if the feed type is not longpoll nor continuous')
58
+
59
+ feed = new follow.Changes({'feed':'longpoll'})
60
+ t.throws(write('stuff'), 'Throw if the "results" line is not sent first')
61
+
62
+ feed = new follow.Changes({'feed':'longpoll'})
63
+ t.doesNotThrow(write('') , 'Empty string is fine waiting for "results"')
64
+ t.doesNotThrow(write('{') , 'This could be the "results" line')
65
+ t.doesNotThrow(write('"resu', 'Another part of the "results" line'))
66
+ t.doesNotThrow(write('') , 'Another empty string is still fine')
67
+ t.doesNotThrow(write('lts":', 'Final part of "results" line still good'))
68
+ t.throws(write(']'), 'First line was not {"results":[')
69
+
70
+ feed = new follow.Changes
71
+ feed.feed = 'continuous'
72
+ t.doesNotThrow(write(''), 'Empty string is fine for a continuous feed')
73
+ t.throws(end('{"results":[\n'), 'Continuous stream does not want a header')
74
+
75
+ feed = new follow.Changes({'feed':'continuous'})
76
+ t.throws(write('hi\n'), 'Continuous stream wants objects')
77
+
78
+ feed = new follow.Changes({'feed':'continuous'})
79
+ t.throws(end('[]\n'), 'Continuous stream wants "real" objects, not Array')
80
+
81
+ feed = new follow.Changes({'feed':'continuous'})
82
+ t.throws(write('{"seq":1,"id":"hi","changes":[{"rev":"1-869df2efe56ff5228e613ceb4d561b35"}]},\n'),
83
+ 'Continuous stream does not want a comma')
84
+
85
+ var types = ['longpoll', 'continuous']
86
+ types.forEach(function(type) {
87
+ var bad_writes = [ {}, null, ['a string (array)'], {'an':'object'}]
88
+ bad_writes.forEach(function(obj) {
89
+ feed = new follow.Changes
90
+ feed.feed = type
91
+
92
+ t.throws(write(obj), 'Throw for bad write to '+type+': ' + util.inspect(obj))
93
+ })
94
+
95
+ feed = new follow.Changes
96
+ feed.feed = type
97
+
98
+ var valid = (type == 'longpoll')
99
+ ? '{"results":[\n{}\n],\n"last_seq":1}'
100
+ : '{"seq":1,"id":"doc"}'
101
+
102
+ t.throws(buf(valid, 'but_invalid_encoding'), 'Throw for buffer with bad encoding')
103
+ })
104
+
105
+ t.end()
106
+
107
+ function buf(data, encoding) {
108
+ return write(new Buffer(data), encoding)
109
+ }
110
+
111
+ function write(data, encoding) {
112
+ if(data === undefined)
113
+ return feed.write('blah')
114
+ return function() { feed.write(data, encoding) }
115
+ }
116
+
117
+ function end(data, encoding) {
118
+ return function() { feed.end(data, encoding) }
119
+ }
120
+ })
121
+
122
+ test('Longpoll feed', function(t) {
123
+ for(var i = 0; i < 2; i++) {
124
+ var feed = new follow.Changes({'feed':'longpoll'})
125
+
126
+ var data = []
127
+ feed.on('data', function(d) { data.push(d) })
128
+
129
+ function encode(data) { return (i == 0) ? data : new Buffer(data) }
130
+ function write(data) { return function() { feed.write(encode(data)) } }
131
+ function end(data) { return function() { feed.end(encode(data)) } }
132
+
133
+ t.doesNotThrow(write('{"results":[') , 'Longpoll header')
134
+ t.doesNotThrow(write('{}') , 'Empty object')
135
+ t.doesNotThrow(write(',{"foo":"bar"},') , 'Comma prefix and suffix')
136
+ t.doesNotThrow(write('{"two":"bar"},') , 'Comma suffix')
137
+ t.doesNotThrow(write('{"three":3},{"four":4}'), 'Two objects on one line')
138
+ t.doesNotThrow(end('],\n"last_seq":3}\n') , 'Longpoll footer')
139
+
140
+ t.equal(data.length, 5, 'Five data events fired')
141
+ t.equal(data[0], '{}', 'First object emitted')
142
+ t.equal(data[1], '{"foo":"bar"}', 'Second object emitted')
143
+ t.equal(data[2], '{"two":"bar"}', 'Third object emitted')
144
+ t.equal(data[3], '{"three":3}', 'Fourth object emitted')
145
+ t.equal(data[4], '{"four":4}', 'Fifth object emitted')
146
+ }
147
+
148
+ t.end()
149
+ })
150
+
151
+ test('Longpoll pause', function(t) {
152
+ var feed = new follow.Changes({'feed':'longpoll'})
153
+ , all = {'results':[{'change':1}, {'second':'change'},{'change':'#3'}], 'last_seq':3}
154
+ , start = new Date
155
+
156
+ var events = []
157
+
158
+ feed.on('data', function(change) {
159
+ change = JSON.parse(change)
160
+ change.elapsed = new Date - start
161
+ events.push(change)
162
+ })
163
+
164
+ feed.once('data', function(data) {
165
+ t.equal(data, '{"change":1}', 'First data event was the first change')
166
+ feed.pause()
167
+ setTimeout(function() { feed.resume() }, 100)
168
+ })
169
+
170
+ feed.on('end', function() {
171
+ t.equal(feed.readable, false, 'Feed is no longer readable')
172
+ events.push('END')
173
+ })
174
+
175
+ setTimeout(check_events, 150)
176
+ feed.end(JSON.stringify(all))
177
+
178
+ function check_events() {
179
+ t.equal(events.length, 3+1, 'Three data events, plus the end event')
180
+
181
+ t.ok(events[0].elapsed < 10, 'Immediate emit first data event')
182
+ t.ok(events[1].elapsed >= 100 && events[1].elapsed < 125, 'About 100ms delay until the second event')
183
+ t.ok(events[2].elapsed - events[1].elapsed < 10, 'Immediate emit of subsequent event after resume')
184
+ t.equal(events[3], 'END', 'End event was fired')
185
+
186
+ t.end()
187
+ }
188
+ })
189
+
190
+ test('Continuous feed', function(t) {
191
+ for(var i = 0; i < 2; i++) {
192
+ var feed = new follow.Changes({'feed':'continuous'})
193
+
194
+ var data = []
195
+ feed.on('data', function(d) { data.push(d) })
196
+ feed.on('end', function() { data.push('END') })
197
+
198
+ var beats = 0
199
+ feed.on('heartbeat', function() { beats += 1 })
200
+
201
+ function encode(data) { return (i == 0) ? data : new Buffer(data) }
202
+ function write(data) { return function() { feed.write(encode(data)) } }
203
+ function end(data) { return function() { feed.end(encode(data)) } }
204
+
205
+ // This also tests whether the feed is compacting or tightening up the JSON.
206
+ t.doesNotThrow(write('{ }\n') , 'Empty object')
207
+ t.doesNotThrow(write('\n') , 'Heartbeat')
208
+ t.doesNotThrow(write('{ "foo" : "bar" }\n') , 'One object')
209
+ t.doesNotThrow(write('{"three":3}\n{ "four": 4}\n'), 'Two objects sent in one chunk')
210
+ t.doesNotThrow(write('') , 'Empty string')
211
+ t.doesNotThrow(write('\n') , 'Another heartbeat')
212
+ t.doesNotThrow(write('') , 'Another empty string')
213
+ t.doesNotThrow(write('{ "end" ') , 'Partial object 1/4')
214
+ t.doesNotThrow(write(':') , 'Partial object 2/4')
215
+ t.doesNotThrow(write('tru') , 'Partial object 3/4')
216
+ t.doesNotThrow(end('e}\n') , 'Partial object 4/4')
217
+
218
+ t.equal(data.length, 6, 'Five objects emitted, plus the end event')
219
+ t.equal(beats, 2, 'Two heartbeats emitted')
220
+
221
+ t.equal(data[0], '{}', 'First object emitted')
222
+ t.equal(data[1], '{"foo":"bar"}', 'Second object emitted')
223
+ t.equal(data[2], '{"three":3}', 'Third object emitted')
224
+ t.equal(data[3], '{"four":4}', 'Fourth object emitted')
225
+ t.equal(data[4], '{"end":true}', 'Fifth object emitted')
226
+ t.equal(data[5], 'END', 'End event fired')
227
+ }
228
+
229
+ t.end()
230
+ })
231
+
232
+ test('Continuous pause', function(t) {
233
+ var feed = new follow.Changes({'feed':'continuous'})
234
+ , all = [{'change':1}, {'second':'change'},{'#3':'change'}]
235
+ , start = new Date
236
+
237
+ var events = []
238
+
239
+ feed.on('end', function() {
240
+ t.equal(feed.readable, false, 'Feed is not readable after "end" event')
241
+ events.push('END')
242
+ })
243
+
244
+ feed.on('data', function(change) {
245
+ change = JSON.parse(change)
246
+ change.elapsed = new Date - start
247
+ events.push(change)
248
+ })
249
+
250
+ feed.once('data', function(data) {
251
+ t.equal(data, '{"change":1}', 'First data event was the first change')
252
+ t.equal(feed.readable, true, 'Feed is readable after first data event')
253
+ feed.pause()
254
+ t.equal(feed.readable, true, 'Feed is readable after pause()')
255
+
256
+ setTimeout(unpause, 100)
257
+ function unpause() {
258
+ t.equal(feed.readable, true, 'Feed is readable just before resume()')
259
+ feed.resume()
260
+ }
261
+ })
262
+
263
+ setTimeout(check_events, 150)
264
+ all.forEach(function(obj) {
265
+ feed.write(JSON.stringify(obj))
266
+ feed.write("\r\n")
267
+ })
268
+ feed.end()
269
+
270
+ function check_events() {
271
+ t.equal(events.length, 3+1, 'Three data events, plus the end event')
272
+
273
+ t.ok(events[0].elapsed < 10, 'Immediate emit first data event')
274
+ t.ok(events[1].elapsed >= 100 && events[1].elapsed < 125, 'About 100ms delay until the second event')
275
+ t.ok(events[2].elapsed - events[1].elapsed < 10, 'Immediate emit of subsequent event after resume')
276
+ t.equal(events[3], 'END', 'End event was fired')
277
+
278
+ t.end()
279
+ }
280
+ })
281
+
282
+ test('Feeds from couch', function(t) {
283
+ t.ok(couch.rtt(), 'RTT to couch is known')
284
+
285
+ var did = 0
286
+ function done() {
287
+ did += 1
288
+ if(did == 2)
289
+ t.end()
290
+ }
291
+
292
+ var types = [ 'longpoll', 'continuous' ]
293
+ types.forEach(function(type) {
294
+ var feed = new follow.Changes({'feed':type})
295
+ setTimeout(check_changes, couch.rtt() * 2)
296
+
297
+ var events = []
298
+ feed.on('data', function(data) { events.push(JSON.parse(data)) })
299
+ feed.on('end', function() { events.push('END') })
300
+
301
+ var uri = couch.DB + '/_changes?feed=' + type
302
+ var req = request({'uri':uri, 'onResponse':true}, on_response)
303
+
304
+ // Disconnect the continuous feed after a while.
305
+ if(type == 'continuous')
306
+ setTimeout(function() { feed.destroy() }, couch.rtt() * 1)
307
+
308
+ function on_response(er, res, body) {
309
+ t.false(er, 'No problem fetching '+type+' feed: ' + uri)
310
+ t.type(body, 'undefined', 'No data in '+type+' callback. This is an onResponse callback')
311
+ t.type(res.body, 'undefined', 'No response body in '+type+' callback. This is an onResponse callback')
312
+ t.ok(req.response, 'The request object has its '+type+' response by now')
313
+
314
+ req.pipe(feed)
315
+
316
+ t.equal(feed.statusCode, 200, 'Upon piping from request, the statusCode is set')
317
+ t.ok('content-type' in feed.headers, 'Upon piping from request, feed has headers set')
318
+ }
319
+
320
+ function check_changes() {
321
+ var expected_count = 3
322
+ if(type == 'longpoll')
323
+ expected_count += 1 // For the "end" event
324
+
325
+ t.equal(events.length, expected_count, 'Change event count for ' + type)
326
+
327
+ t.equal(events[0].seq, 1, 'First '+type+' update sequence id')
328
+ t.equal(events[1].seq, 2, 'Second '+type+' update sequence id')
329
+ t.equal(events[2].seq, 3, 'Third '+type+' update sequence id')
330
+
331
+ t.equal(good_id(events[0]), true, 'First '+type+' update is a good doc id: ' + events[0].id)
332
+ t.equal(good_id(events[1]), true, 'Second '+type+' update is a good doc id: ' + events[1].id)
333
+ t.equal(good_id(events[2]), true, 'Third '+type+' update is a good doc id: ' + events[2].id)
334
+
335
+ if(type == 'longpoll')
336
+ t.equal(events[3], 'END', 'End event fired for '+type)
337
+ else
338
+ t.type(events[3], 'undefined', 'No end event for a destroyed continuous feed')
339
+
340
+ done()
341
+ }
342
+
343
+ var good_ids = {'doc_first':true, 'doc_second':true, 'doc_third':true}
344
+ function good_id(event) {
345
+ var is_good = good_ids[event.id]
346
+ delete good_ids[event.id]
347
+ return is_good
348
+ }
349
+ })
350
+ })
351
+
352
+ test('Pausing and destroying a feed mid-stream', function(t) {
353
+ t.ok(couch.rtt(), 'RTT to couch is known')
354
+ var IMMEDIATE = 20
355
+ , FIRST_PAUSE = couch.rtt() * 8
356
+ , SECOND_PAUSE = couch.rtt() * 12
357
+
358
+ // To be really, really sure that backpressure goes all the way to couch, create more
359
+ // documents than could possibly be buffered. Linux and OSX seem to have a 16k MTU for
360
+ // the local interface, so a few hundred kb worth of document data should cover it.
361
+ couch.make_data(512 * 1024, check)
362
+
363
+ var types = ['longpoll', 'continuous']
364
+ function check(bulk_docs_count) {
365
+ var type = types.shift()
366
+ if(!type)
367
+ return t.end()
368
+
369
+ var feed = new follow.Changes
370
+ feed.feed = type
371
+
372
+ var destroys = 0
373
+ function destroy() {
374
+ destroys += 1
375
+ feed.destroy()
376
+
377
+ // Give one more RTT time for everything to wind down before checking how it all went.
378
+ if(destroys == 1)
379
+ setTimeout(check_events, couch.rtt())
380
+ }
381
+
382
+ var events = {'feed':[], 'http':[], 'request':[]}
383
+ , firsts = {'feed':null, 'http':null, 'request':null}
384
+ function ev(type, value) {
385
+ var now = new Date
386
+ firsts[type] = firsts[type] || now
387
+ events[type].push({'elapsed':now - firsts[type], 'at':now, 'val':value, 'before_destroy':(destroys == 0)})
388
+ }
389
+
390
+ feed.on('heartbeat', function() { ev('feed', 'heartbeat') })
391
+ feed.on('error', function(er) { ev('feed', er) })
392
+ feed.on('close', function() { ev('feed', 'close') })
393
+ feed.on('data', function(data) { ev('feed', data) })
394
+ feed.on('end', function() { ev('feed', 'end') })
395
+
396
+ var data_count = 0
397
+ feed.on('data', function() {
398
+ data_count += 1
399
+ if(data_count == 4) {
400
+ feed.pause()
401
+ setTimeout(function() { feed.resume() }, FIRST_PAUSE)
402
+ }
403
+
404
+ if(data_count == 7) {
405
+ feed.pause()
406
+ setTimeout(function() { feed.resume() }, SECOND_PAUSE - FIRST_PAUSE)
407
+ }
408
+
409
+ if(data_count >= 10)
410
+ destroy()
411
+ })
412
+
413
+ var uri = couch.DB + '/_changes?include_docs=true&feed=' + type
414
+ if(type == 'continuous')
415
+ uri += '&heartbeat=' + Math.floor(couch.rtt())
416
+
417
+ var req = request({'uri':uri, 'onResponse':feed_response})
418
+ req.on('error', function(er) { ev('request', er) })
419
+ req.on('close', function() { ev('request', 'close') })
420
+ req.on('data', function(d) { ev('request', d) })
421
+ req.on('end', function() { ev('request', 'end') })
422
+ req.pipe(feed)
423
+
424
+ function feed_response(er, res) {
425
+ if(er) throw er
426
+
427
+ res.on('error', function(er) { ev('http', er) })
428
+ res.on('close', function() { ev('http', 'close') })
429
+ res.on('data', function(d) { ev('http', d) })
430
+ res.on('end', function() { ev('http', 'end') })
431
+
432
+ t.equal(events.feed.length, 0, 'No feed events yet: ' + type)
433
+ t.equal(events.http.length, 0, 'No http events yet: ' + type)
434
+ t.equal(events.request.length, 0, 'No request events yet: ' + type)
435
+ }
436
+
437
+ function check_events() {
438
+ t.equal(destroys, 1, 'Only necessary to call destroy once: ' + type)
439
+ t.equal(events.feed.length, 10, 'Ten '+type+' data events fired')
440
+ if(events.feed.length != 10)
441
+ events.feed.forEach(function(e, i) {
442
+ console.error((i+1) + ') ' + util.inspect(e))
443
+ })
444
+
445
+ events.feed.forEach(function(event, i) {
446
+ var label = type + ' event #' + (i+1) + ' at ' + event.elapsed + ' ms'
447
+
448
+ t.type(event.val, 'string', label+' was a data string')
449
+ t.equal(event.before_destroy, true, label+' fired before the destroy')
450
+
451
+ var change = null
452
+ t.doesNotThrow(function() { change = JSON.parse(event.val) }, label+' was JSON: ' + type)
453
+ t.ok(change && change.seq > 0 && change.id, label+' was change data: ' + type)
454
+
455
+ // The first batch of events should have fired quickly (IMMEDIATE), then silence. Then another batch
456
+ // of events at the FIRST_PAUSE mark. Then more silence. Then a final batch at the SECOND_PAUSE mark.
457
+ if(i < 4)
458
+ t.ok(event.elapsed < IMMEDIATE, label+' was immediate (within '+IMMEDIATE+' ms)')
459
+ else if(i < 7)
460
+ t.ok(is_almost(event.elapsed, FIRST_PAUSE), label+' was after the first pause (about '+FIRST_PAUSE+' ms)')
461
+ else
462
+ t.ok(is_almost(event.elapsed, SECOND_PAUSE), label+' was after the second pause (about '+SECOND_PAUSE+' ms)')
463
+ })
464
+
465
+ if(type == 'continuous') {
466
+ t.ok(events.http.length >= 10, 'Should have at least ten '+type+' HTTP events')
467
+ t.ok(events.request.length >= 10, 'Should have at least ten '+type+' request events')
468
+
469
+ t.ok(events.http.length < 200, type+' HTTP events ('+events.http.length+') stop before 100')
470
+ t.ok(events.request.length < 200, type+' request events ('+events.request.length+') stop before 100')
471
+
472
+ var frac = events.http.length / bulk_docs_count
473
+ t.ok(frac < 0.10, 'Percent of http events received ('+frac.toFixed(1)+'%) is less than 10% of the data')
474
+
475
+ frac = events.request.length / bulk_docs_count
476
+ t.ok(frac < 0.10, type+' request events received ('+frac.toFixed(1)+'%) is less than 10% of the data')
477
+ }
478
+
479
+ return check(bulk_docs_count)
480
+ }
481
+ }
482
+ })
483
+
484
+ //
485
+ // Utilities
486
+ //
487
+
488
+ function is_almost(actual, expected) {
489
+ var tolerance = 0.10 // 10%
490
+ , diff = Math.abs(actual - expected)
491
+ , fraction = diff / expected
492
+ return fraction < tolerance
493
+ }