vulcan 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }