telephony 1.0.3

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 (365) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +105 -0
  3. data/Rakefile +39 -0
  4. data/app/assets/fonts/telephony/zest-telephony.eot +0 -0
  5. data/app/assets/fonts/telephony/zest-telephony.svg +73 -0
  6. data/app/assets/fonts/telephony/zest-telephony.ttf +0 -0
  7. data/app/assets/fonts/telephony/zest-telephony.woff +0 -0
  8. data/app/assets/images/telephony/backspace.png +0 -0
  9. data/app/assets/images/telephony/icon-spinner.gif +0 -0
  10. data/app/assets/javascripts/telephony/application.js +12 -0
  11. data/app/assets/javascripts/telephony/collections/agents.js +4 -0
  12. data/app/assets/javascripts/telephony/config.js.erb +11 -0
  13. data/app/assets/javascripts/telephony/models/agent.js +103 -0
  14. data/app/assets/javascripts/telephony/models/conversation.js +226 -0
  15. data/app/assets/javascripts/telephony/models/device.js +30 -0
  16. data/app/assets/javascripts/telephony/models/transfer.js +65 -0
  17. data/app/assets/javascripts/telephony/namespace.js +6 -0
  18. data/app/assets/javascripts/telephony/push.js +80 -0
  19. data/app/assets/javascripts/telephony/vendor/backbone.js +1159 -0
  20. data/app/assets/javascripts/telephony/vendor/chosen.jquery.js +1018 -0
  21. data/app/assets/javascripts/telephony/vendor/jquery.form.js +1117 -0
  22. data/app/assets/javascripts/telephony/vendor/pusher.js +1304 -0
  23. data/app/assets/javascripts/telephony/vendor/test_dependencies.js +18 -0
  24. data/app/assets/javascripts/telephony/vendor/underscore.js +839 -0
  25. data/app/assets/javascripts/telephony/views/agents_view.js +69 -0
  26. data/app/assets/javascripts/telephony/views/application_view.js +78 -0
  27. data/app/assets/javascripts/telephony/views/call_queue_view.js +21 -0
  28. data/app/assets/javascripts/telephony/views/conversation_buttons_view.js +15 -0
  29. data/app/assets/javascripts/telephony/views/conversation_view.js +322 -0
  30. data/app/assets/javascripts/telephony/views/status_view.js +36 -0
  31. data/app/assets/javascripts/telephony/views/transfer_view.js +87 -0
  32. data/app/assets/javascripts/telephony/views/twilio_client_view.js +117 -0
  33. data/app/assets/javascripts/telephony/views/widget_view.js +55 -0
  34. data/app/assets/javascripts/telephony/widget.js +5 -0
  35. data/app/assets/javascripts/templates/telephony/agents_view.jst.ejs +8 -0
  36. data/app/assets/javascripts/templates/telephony/call_queue_view.jst.ejs +2 -0
  37. data/app/assets/javascripts/templates/telephony/conversation_buttons_view.jst.ejs +23 -0
  38. data/app/assets/javascripts/templates/telephony/conversation_view.jst.ejs +10 -0
  39. data/app/assets/javascripts/templates/telephony/status_view.jst.ejs +4 -0
  40. data/app/assets/javascripts/templates/telephony/transfer_view.jst.ejs +38 -0
  41. data/app/assets/javascripts/templates/telephony/twilio_client_view.jst.ejs +10 -0
  42. data/app/assets/stylesheets/telephony/font.css +43 -0
  43. data/app/assets/stylesheets/telephony/reset.css.scss +10 -0
  44. data/app/assets/stylesheets/telephony/widget.css.scss +512 -0
  45. data/app/controllers/telephony/agents_controller.rb +46 -0
  46. data/app/controllers/telephony/application_controller.rb +4 -0
  47. data/app/controllers/telephony/call_centers_controller.rb +9 -0
  48. data/app/controllers/telephony/conversations_controller.rb +74 -0
  49. data/app/controllers/telephony/inbound/conversation_queues_controller.rb +21 -0
  50. data/app/controllers/telephony/playable_listeners_controller.rb +43 -0
  51. data/app/controllers/telephony/providers/twilio/application_controller.rb +26 -0
  52. data/app/controllers/telephony/providers/twilio/calls_controller.rb +130 -0
  53. data/app/controllers/telephony/providers/twilio/inbound_calls_controller.rb +54 -0
  54. data/app/controllers/telephony/providers/twilio/musics_controller.rb +12 -0
  55. data/app/controllers/telephony/providers/twilio/voicemails_controller.rb +29 -0
  56. data/app/controllers/telephony/signals/agents/presences_controller.rb +64 -0
  57. data/app/controllers/telephony/transfers_controller.rb +18 -0
  58. data/app/controllers/telephony/twilio_client_controller.rb +37 -0
  59. data/app/controllers/telephony/voicemails_controller.rb +13 -0
  60. data/app/controllers/telephony/widget_controller.rb +10 -0
  61. data/app/helpers/telephony/application_helper.rb +4 -0
  62. data/app/helpers/telephony/calls_helper.rb +13 -0
  63. data/app/models/telephony/agent.rb +186 -0
  64. data/app/models/telephony/agent_state_machine.rb +54 -0
  65. data/app/models/telephony/base.rb +7 -0
  66. data/app/models/telephony/blacklisted_number.rb +5 -0
  67. data/app/models/telephony/call.rb +94 -0
  68. data/app/models/telephony/call_center.rb +26 -0
  69. data/app/models/telephony/call_state_machine.rb +98 -0
  70. data/app/models/telephony/conversation.rb +273 -0
  71. data/app/models/telephony/conversation_state_machine.rb +109 -0
  72. data/app/models/telephony/conversations_presenter.rb +83 -0
  73. data/app/models/telephony/events.rb +6 -0
  74. data/app/models/telephony/events/answer.rb +6 -0
  75. data/app/models/telephony/events/base.rb +118 -0
  76. data/app/models/telephony/events/busy.rb +6 -0
  77. data/app/models/telephony/events/call_answered.rb +22 -0
  78. data/app/models/telephony/events/call_fail.rb +6 -0
  79. data/app/models/telephony/events/complete_hold.rb +28 -0
  80. data/app/models/telephony/events/complete_one_step_transfer.rb +17 -0
  81. data/app/models/telephony/events/complete_resume.rb +28 -0
  82. data/app/models/telephony/events/complete_two_step_transfer.rb +6 -0
  83. data/app/models/telephony/events/conference.rb +6 -0
  84. data/app/models/telephony/events/connect.rb +22 -0
  85. data/app/models/telephony/events/customer_left_two_step_transfer.rb +6 -0
  86. data/app/models/telephony/events/dial_agent.rb +6 -0
  87. data/app/models/telephony/events/ended.rb +19 -0
  88. data/app/models/telephony/events/enqueue.rb +6 -0
  89. data/app/models/telephony/events/fail_one_step_transfer.rb +9 -0
  90. data/app/models/telephony/events/fail_two_step_transfer.rb +6 -0
  91. data/app/models/telephony/events/initialize_widget.rb +15 -0
  92. data/app/models/telephony/events/initiate_hold.rb +6 -0
  93. data/app/models/telephony/events/initiate_one_step_transfer.rb +6 -0
  94. data/app/models/telephony/events/initiate_resume.rb +6 -0
  95. data/app/models/telephony/events/initiate_two_step_transfer.rb +6 -0
  96. data/app/models/telephony/events/leave_two_step_transfer.rb +17 -0
  97. data/app/models/telephony/events/leave_voicemail.rb +22 -0
  98. data/app/models/telephony/events/no_answer.rb +6 -0
  99. data/app/models/telephony/events/play_closed_greeting.rb +6 -0
  100. data/app/models/telephony/events/play_message.rb +6 -0
  101. data/app/models/telephony/events/reject.rb +6 -0
  102. data/app/models/telephony/events/rona.rb +6 -0
  103. data/app/models/telephony/events/start.rb +17 -0
  104. data/app/models/telephony/events/straight_to_voicemail.rb +6 -0
  105. data/app/models/telephony/events/terminate.rb +6 -0
  106. data/app/models/telephony/events/transfer.rb +35 -0
  107. data/app/models/telephony/inbound_conversation_queue.rb +101 -0
  108. data/app/models/telephony/playable.rb +7 -0
  109. data/app/models/telephony/playable_listener.rb +42 -0
  110. data/app/models/telephony/pusher_event_publisher.rb +26 -0
  111. data/app/models/telephony/recording.rb +4 -0
  112. data/app/models/telephony/voicemail.rb +36 -0
  113. data/app/observers/telephony/agent_observer.rb +8 -0
  114. data/app/observers/telephony/call_observer.rb +15 -0
  115. data/app/observers/telephony/conversation_observer.rb +9 -0
  116. data/app/observers/telephony/event_observer.rb +9 -0
  117. data/app/views/layouts/telephony/application.html.erb +14 -0
  118. data/app/views/telephony/providers/twilio/calls/child_detached.builder +5 -0
  119. data/app/views/telephony/providers/twilio/calls/complete_hold.builder +6 -0
  120. data/app/views/telephony/providers/twilio/calls/connect.builder +13 -0
  121. data/app/views/telephony/providers/twilio/calls/dial.builder +14 -0
  122. data/app/views/telephony/providers/twilio/calls/done.builder +5 -0
  123. data/app/views/telephony/providers/twilio/calls/join_conference.builder +7 -0
  124. data/app/views/telephony/providers/twilio/calls/whisper_tone.builder +3 -0
  125. data/app/views/telephony/providers/twilio/inbound_calls/closed_hours.builder +7 -0
  126. data/app/views/telephony/providers/twilio/inbound_calls/create.builder +8 -0
  127. data/app/views/telephony/providers/twilio/inbound_calls/enqueue.builder +8 -0
  128. data/app/views/telephony/providers/twilio/inbound_calls/reject.builder +5 -0
  129. data/app/views/telephony/providers/twilio/inbound_calls/wait_music.builder +5 -0
  130. data/app/views/telephony/providers/twilio/musics/hold.builder +5 -0
  131. data/app/views/telephony/providers/twilio/voicemails/new.builder +6 -0
  132. data/app/views/telephony/twilio_client/index.html.erb +74 -0
  133. data/app/views/telephony/widget/index.erb +3 -0
  134. data/config/cucumber.yml +8 -0
  135. data/config/database.yml +57 -0
  136. data/config/environment.rb +67 -0
  137. data/config/initializers/pusher.rb +21 -0
  138. data/config/initializers/telephony.rb +44 -0
  139. data/config/routes.rb +94 -0
  140. data/config/wopr.yml.example +38 -0
  141. data/db/migrate/20130806213053_bootstrap_db.rb +105 -0
  142. data/db/migrate/20131009204026_create_telephony_blacklisted_numbers.rb +11 -0
  143. data/lib/agent_generator.rb +24 -0
  144. data/lib/tasks/cucumber.rake +65 -0
  145. data/lib/tasks/jasmine.rake +8 -0
  146. data/lib/tasks/telephony_tasks.rake +4 -0
  147. data/lib/telephony.rb +34 -0
  148. data/lib/telephony/concerns/controllers/twilio_request_verifier.rb +39 -0
  149. data/lib/telephony/conversation_data.rb +67 -0
  150. data/lib/telephony/engine.rb +19 -0
  151. data/lib/telephony/error.rb +8 -0
  152. data/lib/telephony/error/agent_on_a_call.rb +2 -0
  153. data/lib/telephony/error/base.rb +3 -0
  154. data/lib/telephony/error/connection.rb +2 -0
  155. data/lib/telephony/error/not_in_progress.rb +2 -0
  156. data/lib/telephony/error/queue_empty.rb +2 -0
  157. data/lib/telephony/helper.rb +11 -0
  158. data/lib/telephony/jobs/agent_offline.rb +28 -0
  159. data/lib/telephony/jobs/pusher_event.rb +21 -0
  160. data/lib/telephony/providers/twilio_provider.rb +162 -0
  161. data/lib/telephony/version.rb +3 -0
  162. data/spec/controllers/telephony/agents_controller_spec.rb +117 -0
  163. data/spec/controllers/telephony/call_centers_controller_spec.rb +25 -0
  164. data/spec/controllers/telephony/conversations_controller_spec.rb +229 -0
  165. data/spec/controllers/telephony/playable_listeners_controller_spec.rb +138 -0
  166. data/spec/controllers/telephony/providers/twilio/calls_controller_spec.rb +33 -0
  167. data/spec/controllers/telephony/providers/twilio/musics_controller_spec.rb +20 -0
  168. data/spec/controllers/telephony/signals/agents/presences_controller_spec.rb +154 -0
  169. data/spec/controllers/telephony/twilio_client_controller_spec.rb +58 -0
  170. data/spec/controllers/telephony/widget_controller_spec.rb +25 -0
  171. data/spec/dummy/Rakefile +7 -0
  172. data/spec/dummy/app/assets/javascripts/application.js +6 -0
  173. data/spec/dummy/app/assets/javascripts/lib/event_logger.js +82 -0
  174. data/spec/dummy/app/assets/javascripts/vendor/backbone.js +1159 -0
  175. data/spec/dummy/app/assets/javascripts/vendor/chosen.jquery.js +1018 -0
  176. data/spec/dummy/app/assets/javascripts/vendor/jquery.form.js +1117 -0
  177. data/spec/dummy/app/assets/javascripts/vendor/underscore.js +839 -0
  178. data/spec/dummy/app/assets/stylesheets/application.css +23 -0
  179. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  180. data/spec/dummy/app/controllers/widget_host_controller.rb +2 -0
  181. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  182. data/spec/dummy/app/views/layouts/application.html.erb +12 -0
  183. data/spec/dummy/app/views/widget_host/index.html.erb +53 -0
  184. data/spec/dummy/config.ru +4 -0
  185. data/spec/dummy/config/application.rb +61 -0
  186. data/spec/dummy/config/boot.rb +10 -0
  187. data/spec/dummy/config/call_centers.yml +35 -0
  188. data/spec/dummy/config/call_centers.yml.example +35 -0
  189. data/spec/dummy/config/database.yml +56 -0
  190. data/spec/dummy/config/environment.rb +5 -0
  191. data/spec/dummy/config/environments/cucumber.rb +39 -0
  192. data/spec/dummy/config/environments/development.rb +39 -0
  193. data/spec/dummy/config/environments/production.rb +67 -0
  194. data/spec/dummy/config/environments/test.rb +39 -0
  195. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  196. data/spec/dummy/config/initializers/inflections.rb +15 -0
  197. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  198. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  199. data/spec/dummy/config/initializers/session_store.rb +8 -0
  200. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  201. data/spec/dummy/config/locales/en.yml +5 -0
  202. data/spec/dummy/config/pusher.yml +14 -0
  203. data/spec/dummy/config/pusher.yml.example +14 -0
  204. data/spec/dummy/config/routes.rb +5 -0
  205. data/spec/dummy/config/sms_whitelist.json.example +4 -0
  206. data/spec/dummy/config/twilio.yml +57 -0
  207. data/spec/dummy/config/twilio.yml.example +57 -0
  208. data/spec/dummy/db/schema.rb +122 -0
  209. data/spec/dummy/db/seeds.rb +6 -0
  210. data/spec/dummy/log/production.log +156 -0
  211. data/spec/dummy/log/test.log +13812 -0
  212. data/spec/dummy/public/404.html +26 -0
  213. data/spec/dummy/public/422.html +26 -0
  214. data/spec/dummy/public/500.html +25 -0
  215. data/spec/dummy/public/assets/application-628309db7ce8c0abdd91f8f796881599.js +32 -0
  216. data/spec/dummy/public/assets/application-628309db7ce8c0abdd91f8f796881599.js.gz +0 -0
  217. data/spec/dummy/public/assets/application-6c551430f3bdd32ba57c9c41e5f91846.css +24 -0
  218. data/spec/dummy/public/assets/application-6c551430f3bdd32ba57c9c41e5f91846.css.gz +0 -0
  219. data/spec/dummy/public/assets/telephony/backspace-9f5b195e049ed90eb20ecdda6e264b0a.png +0 -0
  220. data/spec/dummy/public/assets/telephony/icon-spinner-ddbae473a70c5425810aeee31d4a8ad7.gif +0 -0
  221. data/spec/dummy/public/assets/telephony/zest-telephony-0e6b13673634da80de9ae09bcca253e3.eot +0 -0
  222. data/spec/dummy/public/assets/telephony/zest-telephony-37f394757ccb11b978f16d9fd32cb3b5.ttf +0 -0
  223. data/spec/dummy/public/assets/telephony/zest-telephony-e7efdbc60c0a1c8404951bc79c7fb3a8.woff +0 -0
  224. data/spec/dummy/public/assets/telephony/zest-telephony-f34287df626936908408f53a33db9e83.svg +73 -0
  225. data/spec/dummy/public/favicon.ico +0 -0
  226. data/spec/dummy/script/rails +6 -0
  227. data/spec/dummy/tmp/cache/assets/C98/020/sprockets%2Fa83f8254d688334ff179984e423f502f +0 -0
  228. data/spec/dummy/tmp/cache/assets/CA4/550/sprockets%2F407f9191fe573f2435c55c017cb0d022 +0 -0
  229. data/spec/dummy/tmp/cache/assets/CAA/6A0/sprockets%2F3265550e2d2c7f5a864ca85d255504c2 +0 -0
  230. data/spec/dummy/tmp/cache/assets/CB0/F60/sprockets%2F462f9efed31e6710732376ae06c27208 +0 -0
  231. data/spec/dummy/tmp/cache/assets/CB6/E20/sprockets%2Fa7092cd9f16917099d754f1380af13e1 +0 -0
  232. data/spec/dummy/tmp/cache/assets/CC7/9F0/sprockets%2F0517100815cd45c43fec1d8510fcb630 +0 -0
  233. data/spec/dummy/tmp/cache/assets/CCA/BF0/sprockets%2F16ea15e0b5d367c34b812612a4207e3e +0 -0
  234. data/spec/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  235. data/spec/dummy/tmp/cache/assets/CF1/960/sprockets%2F36ef26985821ec702c74dae4af789903 +0 -0
  236. data/spec/dummy/tmp/cache/assets/D07/F30/sprockets%2Fd12862f20df0c9ef7aa2b4216d247079 +0 -0
  237. data/spec/dummy/tmp/cache/assets/D09/BF0/sprockets%2F5c8595efaa8cc5235a09801c60e25a16 +0 -0
  238. data/spec/dummy/tmp/cache/assets/D0D/250/sprockets%2F5e759d66e374910dae5a51b221e6e3a4 +0 -0
  239. data/spec/dummy/tmp/cache/assets/D0D/E30/sprockets%2Fa282879e0788ba133f5bdc211cf134e8 +0 -0
  240. data/spec/dummy/tmp/cache/assets/D11/8E0/sprockets%2F2a193a8af29963f1113bef05788e02ff +0 -0
  241. data/spec/dummy/tmp/cache/assets/D14/270/sprockets%2F7f95a752910ed33d728532f8fabd340d +0 -0
  242. data/spec/dummy/tmp/cache/assets/D17/BE0/sprockets%2F58749d7aea4ca766c2520801f0f96b9b +0 -0
  243. data/spec/dummy/tmp/cache/assets/D18/270/sprockets%2Fbb2d88813b1795a97e9a7c14100eef77 +0 -0
  244. data/spec/dummy/tmp/cache/assets/D22/310/sprockets%2Fbae9148ce1229214dfbb0c425b0054d0 +0 -0
  245. data/spec/dummy/tmp/cache/assets/D30/EB0/sprockets%2F2c668b88beb4c9805d91ed4587f549e7 +0 -0
  246. data/spec/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  247. data/spec/dummy/tmp/cache/assets/D3C/690/sprockets%2F681ba175fdd2da8d590b7e624b6135a6 +0 -0
  248. data/spec/dummy/tmp/cache/assets/D44/BC0/sprockets%2F7214f66dd91fdd51f00c3882293f5eea +0 -0
  249. data/spec/dummy/tmp/cache/assets/D46/FA0/sprockets%2F4f9543cd9e68ba3141fd488afb13681b +0 -0
  250. data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  251. data/spec/dummy/tmp/cache/assets/D51/9A0/sprockets%2Fb6e4cbcf30f6f611671695f79ea846d5 +0 -0
  252. data/spec/dummy/tmp/cache/assets/D55/920/sprockets%2F0a02242aa4c4febcc796419aba103c69 +0 -0
  253. data/spec/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  254. data/spec/dummy/tmp/cache/assets/D5F/6F0/sprockets%2F155e7bc44f326371cce0ca8bc5b2414e +0 -0
  255. data/spec/dummy/tmp/cache/assets/D6B/250/sprockets%2Fe4eecce7a3371b11038c92f07cb28e67 +0 -0
  256. data/spec/dummy/tmp/cache/assets/D6B/E70/sprockets%2F103a456ae3692e3cca20b7696fdee8f0 +0 -0
  257. data/spec/dummy/tmp/cache/assets/D7F/AD0/sprockets%2Fb1c02d12303b1fe2f981771cbb0c4cbd +0 -0
  258. data/spec/dummy/tmp/cache/assets/D81/D00/sprockets%2F0c68b6d9dce98681a2a710d9fc3f7e39 +0 -0
  259. data/spec/dummy/tmp/cache/assets/D94/4D0/sprockets%2F4608a5bdf27ddf11bcaed1375911e9a2 +0 -0
  260. data/spec/dummy/tmp/cache/assets/D9A/580/sprockets%2F433005b19aaf7ed5cba9c7f8cd30b775 +0 -0
  261. data/spec/dummy/tmp/cache/assets/DA4/270/sprockets%2F860ab3bd30204e02c0a5d82bd4bbabe1 +0 -0
  262. data/spec/dummy/tmp/cache/assets/DA4/480/sprockets%2F53e39285bb57a2ddb6ff856b2eb491da +0 -0
  263. data/spec/dummy/tmp/cache/assets/DA6/3C0/sprockets%2F587c7e35f47442eadaa530acc5e9f5e6 +0 -0
  264. data/spec/dummy/tmp/cache/assets/DAB/DA0/sprockets%2Fb8fb5e8518ed3d38a7f6a8f18a9208aa +0 -0
  265. data/spec/dummy/tmp/cache/assets/DB2/700/sprockets%2Ffa85cb79b94fce1b8344d983ea863d2e +0 -0
  266. data/spec/dummy/tmp/cache/assets/DCC/4F0/sprockets%2Fbc0f3c61e16afb5a285ba8b39846ed8b +0 -0
  267. data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  268. data/spec/dummy/tmp/cache/assets/DDC/BE0/sprockets%2F2c0a4d4675a61b014aedbbabb3f071fc +0 -0
  269. data/spec/dummy/tmp/cache/assets/E04/640/sprockets%2F3c5ddf2bbefd9d70f1b506b519cf856e +0 -0
  270. data/spec/dummy/tmp/cache/assets/E04/7F0/sprockets%2F35f1b2ff729a5ec21f6dc5f7d6faa67b +0 -0
  271. data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  272. data/spec/dummy/tmp/cache/assets/E07/230/sprockets%2Febd4f2dd708879d593aff008ccb75acc +0 -0
  273. data/spec/dummy/tmp/cache/assets/E17/9D0/sprockets%2F8db2b79f6c4eabe7a9ebe7a7898ec066 +0 -0
  274. data/spec/dummy/tmp/cache/assets/E31/F10/sprockets%2Fcebacf56667da1cc2961b59fd922eafb +0 -0
  275. data/spec/factories/agent.rb +26 -0
  276. data/spec/factories/calls.rb +106 -0
  277. data/spec/factories/conversations.rb +171 -0
  278. data/spec/factories/events.rb +250 -0
  279. data/spec/factories/playable_listeners.rb +6 -0
  280. data/spec/factories/recordings.rb +6 -0
  281. data/spec/factories/voicemails.rb +8 -0
  282. data/spec/fixtures/vcr_cassettes/Authenticating_an_online_user.yml +90 -0
  283. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/creates_a_new_call.yml +42 -0
  284. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/creates_a_new_conversation.yml +42 -0
  285. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/returns_the_conversation_as_JSON.yml +42 -0
  286. data/spec/fixtures/vcr_cassettes/Pusher_channel_as_JSON.yml +90 -0
  287. data/spec/fixtures/vcr_cassettes/Pusher_to_publish_the_event.yml +32 -0
  288. data/spec/fixtures/vcr_cassettes/Reloading_the_widget_during_a_call/_create/during_a_call/after_a_failed_transfer/pushes_the_event_for_the_active_call.yml +1110 -0
  289. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/creates_a_new_call.yml +45 -0
  290. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/creates_a_new_conversation.yml +43 -0
  291. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/returns_the_conversation_as_JSON.yml +44 -0
  292. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/allows_the_phone_to_ring_for_60_seconds.yml +42 -0
  293. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/includes_a_status_change_callback_url.yml +42 -0
  294. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/places_a_call.yml +42 -0
  295. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_dial_into_conference/allows_the_phone_to_ring_for_15_seconds.yml +42 -0
  296. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_dial_into_conference/places_a_call_to_redirect_the_participant_to_a_conference.yml +42 -0
  297. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_hangup/by_default/hangs_up_the_call.yml +42 -0
  298. data/spec/fixtures/vcr_cassettes/buying_number_for_an_area_code.yml +42 -0
  299. data/spec/fixtures/vcr_cassettes/returns_success.yml +90 -0
  300. data/spec/javascripts/helpers/jasmine-jquery.js +546 -0
  301. data/spec/javascripts/helpers/mock-ajax.js +207 -0
  302. data/spec/javascripts/support/jasmine.yml +80 -0
  303. data/spec/javascripts/telephony/models/agent_spec.js +40 -0
  304. data/spec/javascripts/telephony/models/conversation_spec.js +515 -0
  305. data/spec/javascripts/telephony/models/device_spec.js +80 -0
  306. data/spec/javascripts/telephony/models/transfer_spec.js +22 -0
  307. data/spec/javascripts/telephony/push_spec.js +427 -0
  308. data/spec/javascripts/telephony/views/agents_view_spec.js +101 -0
  309. data/spec/javascripts/telephony/views/application_view_spec.js +74 -0
  310. data/spec/javascripts/telephony/views/conversation_view_spec.js +626 -0
  311. data/spec/javascripts/telephony/views/status_view_spec.js +30 -0
  312. data/spec/javascripts/telephony/views/transfer_view_spec.js +187 -0
  313. data/spec/javascripts/telephony/views/twilio_client_view_spec.js +77 -0
  314. data/spec/javascripts/telephony/views/widget_view_spec.js +20 -0
  315. data/spec/lib/telephony/concerns/controllers/twilio_request_verifier_spec.rb +77 -0
  316. data/spec/lib/telephony/conversation_data_spec.rb +342 -0
  317. data/spec/lib/telephony/helper_spec.rb +24 -0
  318. data/spec/lib/telephony/jobs/agent_offline_spec.rb +37 -0
  319. data/spec/lib/telephony/jobs/pusher_event_spec.rb +39 -0
  320. data/spec/lib/telephony/providers/twilio_provider_spec.rb +439 -0
  321. data/spec/lib/telephony_spec.rb +104 -0
  322. data/spec/models/telephony/agent_spec.rb +479 -0
  323. data/spec/models/telephony/agent_state_machine_spec.rb +131 -0
  324. data/spec/models/telephony/call_center_spec.rb +32 -0
  325. data/spec/models/telephony/call_spec.rb +597 -0
  326. data/spec/models/telephony/call_state_machine_spec.rb +472 -0
  327. data/spec/models/telephony/conversation_spec.rb +751 -0
  328. data/spec/models/telephony/conversation_state_machine_spec.rb +387 -0
  329. data/spec/models/telephony/conversations_presenter_spec.rb +40 -0
  330. data/spec/models/telephony/event_spec.rb +716 -0
  331. data/spec/models/telephony/inbound_conversation_queue_spec.rb +243 -0
  332. data/spec/models/telephony/playable_listener_spec.rb +43 -0
  333. data/spec/models/telephony/pusher_event_publisher_spec.rb +36 -0
  334. data/spec/models/telephony/recording_spec.rb +6 -0
  335. data/spec/models/telephony/voicemail_spec.rb +96 -0
  336. data/spec/observers/telephony/agent_observer_spec.rb +21 -0
  337. data/spec/observers/telephony/event_observer_spec.rb +19 -0
  338. data/spec/requests/create_conversation_spec.rb +39 -0
  339. data/spec/requests/dequeue_call_spec.rb +42 -0
  340. data/spec/requests/list_conversations_spec.rb +33 -0
  341. data/spec/requests/presences_spec.rb +51 -0
  342. data/spec/requests/providers/twilio/calls/child_answered_spec.rb +24 -0
  343. data/spec/requests/providers/twilio/calls/child_detached_spec.rb +249 -0
  344. data/spec/requests/providers/twilio/calls/dial_spec.rb +132 -0
  345. data/spec/requests/providers/twilio/calls/done_spec.rb +69 -0
  346. data/spec/requests/providers/twilio/calls/join_conference_spec.rb +56 -0
  347. data/spec/requests/providers/twilio/calls/leave_queue_spec.rb +44 -0
  348. data/spec/requests/providers/twilio/calls/parent_answered_spec.rb +59 -0
  349. data/spec/requests/providers/twilio/inbound/connect_dequeued_call_spec.rb +124 -0
  350. data/spec/requests/providers/twilio/inbound/enqueue_inbound_call_spec.rb +108 -0
  351. data/spec/requests/providers/twilio/inbound/enqueued_call_wait_music_spec.rb +13 -0
  352. data/spec/requests/providers/twilio/voicemails/leave_voicemail_spec.rb +78 -0
  353. data/spec/requests/search_conversations_spec.rb +101 -0
  354. data/spec/requests/transfer_spec.rb +65 -0
  355. data/spec/requests/voicemail_api_spec.rb +58 -0
  356. data/spec/spec_helper.rb +27 -0
  357. data/spec/support/factory_girl.rb +5 -0
  358. data/spec/support/matchers/be_complete_hold.rb +20 -0
  359. data/spec/support/matchers/be_whisper_tone.rb +35 -0
  360. data/spec/support/matchers/join_conference.rb +23 -0
  361. data/spec/support/nokogiri.rb +1 -0
  362. data/spec/support/pusher_helper.rb +24 -0
  363. data/spec/support/vcr.rb +12 -0
  364. data/spec/support/webmock.rb +1 -0
  365. metadata +692 -0
@@ -0,0 +1,3 @@
1
+ class Telephony::Error::Base < StandardError
2
+ end
3
+
@@ -0,0 +1,2 @@
1
+ class Telephony::Error::Connection < Telephony::Error::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Telephony::Error::NotInProgress < Telephony::Error::Base
2
+ end
@@ -0,0 +1,2 @@
1
+ class Telephony::Error::QueueEmpty < Telephony::Error::Base
2
+ end
@@ -0,0 +1,11 @@
1
+ module Telephony
2
+ module NumberHelper
3
+ def normalize_number(number)
4
+ number.gsub(/\D/, '').gsub(/^1/, '')
5
+ end
6
+
7
+ def extract_area_code(number)
8
+ normalize_number(number)[0..2]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ module Telephony
2
+ module Jobs
3
+ class AgentOffline
4
+ attr_accessor :id, :timestamp
5
+
6
+ def initialize id, timestamp
7
+ @id = id
8
+ @timestamp = timestamp
9
+ end
10
+
11
+ def update_status
12
+ Agent.find_with_lock(id) do |agent|
13
+ agent.update_status_by_event Agent::OFFLINE, timestamp
14
+ end
15
+ end
16
+
17
+ def perform
18
+ update_status
19
+ end
20
+
21
+ def failure
22
+ Rails.logger.error "#{self.class}: #{self.as_json}"
23
+ ActiveSupport::Notifications
24
+ .instrument("telephony.agent_offline_failure", self)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ module Telephony
2
+ module Jobs
3
+ class PusherEvent
4
+ attr_reader :event
5
+
6
+ def initialize(event)
7
+ @event = event
8
+ end
9
+
10
+ def perform
11
+ Telephony::PusherEventPublisher.push(@event)
12
+ end
13
+
14
+ def failure
15
+ Rails.logger.error "#{self.class}: #{self.as_json}"
16
+ ActiveSupport::Notifications
17
+ .instrument("telephony.agent_offline_failure", self)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,162 @@
1
+ module Telephony
2
+ module Providers
3
+ class TwilioProvider
4
+ include Telephony::NumberHelper
5
+
6
+ RING_TIMEOUT_IN_SECONDS = 60
7
+ RING_TRANSFER_TIMEOUT_IN_SECONDS = 15
8
+ REQUEST_TIMEOUT_IN_SECONDS = 6
9
+
10
+ attr_accessor :account_sid,
11
+ :auth_token,
12
+ :outbound_caller_id,
13
+ :callback_root,
14
+ :voice_url,
15
+ :sms_application_sid,
16
+ :twilio_provider_url
17
+
18
+ def initialize(config)
19
+ self.account_sid = config[:account_sid]
20
+ self.auth_token = config[:auth_token]
21
+ self.outbound_caller_id = config[:outbound_caller_id]
22
+ self.callback_root = config[:callback_root]
23
+ self.voice_url = config[:voice_url]
24
+ self.sms_application_sid = config[:sms_application_sid]
25
+ self.twilio_provider_url = "#{callback_root}/providers/twilio/calls"
26
+ @incoming_phone_numbers_cache = {}
27
+ end
28
+
29
+ def call(call_id, destination, caller_id)
30
+ create_call number: destination,
31
+ caller_id: caller_id,
32
+ callback_url: "#{twilio_provider_url}/#{call_id}/connect",
33
+ call_id: call_id,
34
+ timeout: RING_TIMEOUT_IN_SECONDS
35
+ end
36
+
37
+ def redirect_to_dial(call_id, sid)
38
+ redirect_or_raise sid, "#{twilio_provider_url}/#{call_id}/dial"
39
+ end
40
+
41
+ def redirect_to_inbound_connect(csr_id, sid)
42
+ redirect_or_raise sid, "#{callback_root}/providers/twilio/inbound_calls/connect?csr_id=#{csr_id}"
43
+ end
44
+
45
+ def redirect_to_conference(call_id, sid)
46
+ redirect_or_raise sid, "#{twilio_provider_url}/#{call_id}/join_conference"
47
+ end
48
+
49
+ def redirect_to_hold(call_id, sid)
50
+ redirect_or_raise sid, "#{twilio_provider_url}/#{call_id}/complete_hold"
51
+ end
52
+
53
+ def dial_into_conference(call_id, destination, caller_id)
54
+ create_call number: destination,
55
+ caller_id: caller_id,
56
+ callback_url: "#{twilio_provider_url}/#{call_id}/join_conference",
57
+ call_id: call_id,
58
+ timeout: RING_TRANSFER_TIMEOUT_IN_SECONDS
59
+ end
60
+
61
+ def hangup(sid)
62
+ call = client.account.calls.find sid
63
+ call.hangup
64
+ rescue => error
65
+ log_error(error, "Error during hangup for call sid: '#{sid}'")
66
+ false
67
+ end
68
+
69
+ def caller_id_for(area_code)
70
+ incoming_phone_number_from_cache(area_code) ||
71
+ fetch_existing_incoming_phone_numbers(area_code) ||
72
+ buy_number_for_area_code(area_code) ||
73
+ normalize_number(outbound_caller_id)
74
+ end
75
+
76
+ def buy_number_for_area_code(area_code)
77
+ number = buy_number(area_code)
78
+ @incoming_phone_numbers_cache[area_code] = number
79
+ number
80
+ rescue Exception => error
81
+ Rails.logger.info "Failed attempt to buy a phone number in area code '#{area_code}': #{error.message}"
82
+ nil
83
+ end
84
+
85
+ def uncallable_number
86
+ 'this-number-is-not-whitelisted'
87
+ end
88
+
89
+ def call_ended?(sid)
90
+ call = client.account.calls.find sid
91
+ call.status.in?(%w(completed failed busy no-answer))
92
+ rescue ::Twilio::REST::RequestError, NoMethodError
93
+ false
94
+ rescue => error
95
+ log_error(error, "Error when verifying if call ended")
96
+ false
97
+ end
98
+
99
+ def client
100
+ @client ||= ::Twilio::REST::Client.new account_sid, auth_token, timeout: REQUEST_TIMEOUT_IN_SECONDS
101
+ end
102
+
103
+ private
104
+
105
+ def incoming_phone_number_from_cache(area_code)
106
+ @incoming_phone_numbers_cache[area_code]
107
+ end
108
+
109
+ def fetch_existing_incoming_phone_numbers(area_code)
110
+ raise_on_error('Failed to fetch incoming phone numbers') do
111
+ numbers = client.account.incoming_phone_numbers.list(phone_number: "+1#{area_code}*******")
112
+ numbers.each do |num|
113
+ @incoming_phone_numbers_cache[extract_area_code(num.phone_number)] = normalize_number(num.phone_number)
114
+ end
115
+ @incoming_phone_numbers_cache[area_code]
116
+ end
117
+ end
118
+
119
+ def buy_number(area_code)
120
+ response = client.account.incoming_phone_numbers.create area_code: area_code,
121
+ voice_url: voice_url,
122
+ sms_application_sid: sms_application_sid
123
+ normalize_number(response.phone_number)
124
+ end
125
+
126
+ def create_call(args)
127
+ raise_on_error('Failed to create a call') do
128
+ client.account.calls.create(from: args[:caller_id],
129
+ to: args[:number],
130
+ url: args[:callback_url],
131
+ status_callback: "#{twilio_provider_url}/#{args[:call_id]}/done",
132
+ timeout: args[:timeout])
133
+ end
134
+ end
135
+
136
+ def redirect_or_raise(sid, to)
137
+ raise_on_error("Failed to redirect #{sid} to #{to}") do
138
+ call = client.account.calls.find sid
139
+ call.redirect_to to
140
+ true
141
+ end
142
+ end
143
+
144
+ def raise_on_error(msg = 'Error')
145
+ begin
146
+ yield
147
+ rescue => error
148
+ log_error(error, msg)
149
+ if error.respond_to?(:code) && error.code == 21220
150
+ raise Telephony::Error::NotInProgress.new("#{msg} - #{error.message}")
151
+ else
152
+ raise Telephony::Error::Connection.new("#{msg} - #{error.message}")
153
+ end
154
+ end
155
+ end
156
+
157
+ def log_error(error, msg)
158
+ Rails.logger.error "Twilio provider - #{msg} - #{error.class} - #{error.message}"
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,3 @@
1
+ module Telephony
2
+ VERSION = '1.0.3'
3
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ module Telephony
4
+ describe AgentsController do
5
+ before do
6
+ @routes = Engine.routes
7
+ end
8
+
9
+ describe "#terminate_active_call" do
10
+ context "given an invalid csr id" do
11
+ before do
12
+ post :terminate_active_call, id: :bogus_id
13
+ end
14
+ it "responds with a status 400" do
15
+ response.should be_bad_request
16
+ end
17
+ end
18
+ context "given a valid csr id" do
19
+ before do
20
+ create :available_agent, :csr_id => 123
21
+ Agent.any_instance.should_receive(:terminate_active_call)
22
+ post :terminate_active_call, id: 123
23
+ end
24
+ it "responds successfully" do
25
+ response.should be_success
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#update" do
31
+ context "given an invalid agent params" do
32
+ before do
33
+ agent = create :invalid_agent
34
+
35
+ put :update,
36
+ csr_phone_type: 'invalid',
37
+ csr_type: "B",
38
+ csr_id: agent.csr_id,
39
+ id: agent.csr_id
40
+ end
41
+
42
+ it "responds with a status 400" do
43
+ response.should be_bad_request
44
+ end
45
+
46
+ it "renders errors" do
47
+ body = JSON.parse response.body
48
+ body['errors'].should == ["Phone type is not included in the list"]
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#status' do
54
+ before do
55
+ @agent = create :available_agent, :csr_id => 123
56
+ put :status, :event => 'not_available', :id => @agent.csr_id
57
+ @agent.reload
58
+ end
59
+
60
+ it 'transitions status based on the given event' do
61
+ @agent.status.should == Telephony::Agent::NOT_AVAILABLE
62
+ end
63
+
64
+ it 'returns the updated agent as JSON' do
65
+ json = JSON response.body
66
+ json.should include('status' => @agent.status)
67
+ json.should include('csr_id' => @agent.csr_id)
68
+ end
69
+ end
70
+
71
+ describe '#show_by_csr_id' do
72
+ before do
73
+ @agent = create :available_agent, :csr_id => 123
74
+ Agent.any_instance.stub(:active_conversation_id).and_return(321)
75
+ get :show_by_csr_id, :csr_id => @agent.csr_id
76
+ end
77
+
78
+ it 'returns the agent' do
79
+ json = JSON response.body
80
+ json.should include('active_conversation_id' => 321)
81
+ end
82
+ end
83
+
84
+ describe "#index" do
85
+ context "by default" do
86
+ it "returns a list of agents" do
87
+ create :available_agent, csr_id: 123
88
+ create :offline_agent, csr_id: 11
89
+ create :available_agent, csr_id: 12
90
+ get :index
91
+
92
+ res = JSON(response.body)
93
+ res.size.should == 3
94
+ res[0].should include('csr_id' => 123)
95
+ res[1].should include('csr_id' => 12)
96
+ res[2].should include('csr_id' => 11)
97
+ end
98
+ end
99
+
100
+ context "given a csr id" do
101
+ it "returns a list of transferable agents for that agent" do
102
+ agent = create :available_agent,
103
+ csr_id: 123,
104
+ transferable_agents: [11, 12]
105
+ create :offline_agent, csr_id: 11
106
+ create :available_agent, csr_id: 12
107
+ get :index, csr_id: agent.csr_id
108
+
109
+ res = JSON(response.body)
110
+ res.size.should == 2
111
+ res[0].should include('csr_id' => 12)
112
+ res[1].should include('csr_id' => 11)
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ module Telephony
4
+ describe CallCentersController do
5
+
6
+ before do
7
+ @routes = Engine.routes
8
+ end
9
+
10
+ describe '#index' do
11
+ before do
12
+ get :index
13
+ end
14
+
15
+ it 'returns a list of the available call centers' do
16
+ response.should be_ok
17
+ response.content_type.should == "application/json"
18
+
19
+ call_centers = JSON.parse(response.body)
20
+ call_centers.first['name'].should == 'hollywood'
21
+ call_centers.first['host'].should == '192.168.1.1'
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,229 @@
1
+ require "spec_helper"
2
+
3
+ module Telephony
4
+ describe ConversationsController do
5
+ before do
6
+ @routes = Engine.routes
7
+ end
8
+
9
+ describe '#hold' do
10
+ context 'when hold works' do
11
+ before do
12
+ @conversation = mock(id: 1)
13
+ @customer = mock
14
+
15
+ Conversation.stub(:find).and_return(@conversation)
16
+ @conversation.stub(:hold!).and_return true
17
+ end
18
+
19
+ it "tells twilio to redirect the customer's call leg to the hold queue" do
20
+ @conversation.should_receive(:hold!)
21
+ xhr :post, :hold, id: @conversation.id
22
+ end
23
+
24
+ it "renders an empty json" do
25
+ xhr :post, :hold, id: @conversation.id
26
+
27
+ body = JSON.parse response.body
28
+ body.should be_empty
29
+ end
30
+ end
31
+
32
+ context 'when hold fails' do
33
+ before do
34
+ @conversation = Conversation.new
35
+ @customer = mock
36
+
37
+ Conversation.stub(:find).and_return(@conversation)
38
+ end
39
+
40
+ context 'because of a twilio issue' do
41
+ before do
42
+ @conversation.stub(:hold!).and_raise(Telephony::Error::Connection.new("Some error"))
43
+ end
44
+
45
+ it "renders json errors" do
46
+ xhr :post, :hold, id: 1
47
+
48
+ body = JSON.parse response.body
49
+ body['errors'].first.should == 'Hold failed. Please try again in a few seconds.'
50
+ body['errors'].last.should == 'Some error'
51
+ end
52
+ end
53
+
54
+ context 'because the customer has hung up' do
55
+ before do
56
+ @conversation.stub(:hold!).and_raise(Telephony::Error::NotInProgress.new("Some error"))
57
+ end
58
+
59
+ it "renders json errors" do
60
+ xhr :post, :hold, id: 1
61
+
62
+ body = JSON.parse response.body
63
+ body['errors'].first.should == 'Hold failed. Please try again in a few seconds.'
64
+ body['errors'].last.should == 'Some error'
65
+ end
66
+ end
67
+
68
+ context 'because the call is in the wrong state' do
69
+ it "renders json errors" do
70
+ xhr :post, :hold, id: 1
71
+
72
+ body = JSON.parse response.body
73
+ body['errors'].first.should == 'Hold failed. Please try again in a few seconds.'
74
+ body['errors'].last.should =~ /cannot transition/
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "#resume" do
81
+ context "when resume works" do
82
+ before do
83
+ @conversation = Conversation.new
84
+
85
+ Conversation.stub(:find).and_return(@conversation)
86
+ @conversation.stub(:resume!).and_return true
87
+ end
88
+
89
+ it "tells twilio to redirect the customer's call leg to the conference's room" do
90
+ @conversation.should_receive :resume!
91
+ xhr :post, :resume, id: 1
92
+
93
+ body = JSON.parse response.body
94
+ body.should be_empty
95
+ end
96
+ end
97
+
98
+ context 'when resume fails' do
99
+ before do
100
+ @conversation = Conversation.new
101
+
102
+ Conversation.stub(:find).and_return(@conversation)
103
+ end
104
+
105
+ context 'because of a twilio issue' do
106
+ before do
107
+ @conversation.stub(:resume!).and_raise(Telephony::Error::Connection.new("Some error"))
108
+ end
109
+
110
+ it "renders json errors" do
111
+ xhr :post, :resume, id: 1
112
+
113
+ body = JSON.parse response.body
114
+ body['errors'].first.should == 'Resume failed. Please try again in a few seconds.'
115
+ body['errors'].last.should == 'Some error'
116
+ end
117
+ end
118
+
119
+ context 'because the customer has hung up' do
120
+ before do
121
+ @conversation.stub(:resume!).and_raise(Telephony::Error::NotInProgress.new("Some error"))
122
+ end
123
+
124
+ it "renders json errors" do
125
+ xhr :post, :resume, id: 1
126
+
127
+ body = JSON.parse response.body
128
+ body['errors'].first.should == 'Resume failed. Please try again in a few seconds.'
129
+ body['errors'].last.should == 'Some error'
130
+ end
131
+ end
132
+
133
+ context 'because the call is in the wrong state' do
134
+ it "renders json errors" do
135
+ xhr :post, :resume, id: 1
136
+
137
+ body = JSON.parse response.body
138
+ body['errors'].first.should == 'Resume failed. Please try again in a few seconds.'
139
+ body['errors'].last.should =~ /cannot transition/
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "#search" do
146
+ before do
147
+ @conversation = create :conversation
148
+ xhr :get, :search
149
+ end
150
+
151
+ it "returns conversations as json" do
152
+ body = JSON.parse response.body
153
+ body['conversations'][0]['id'].should == @conversation.id
154
+ body['total_count'].should == 1
155
+ end
156
+ end
157
+
158
+ describe "#create", :vcr do
159
+ context "by default" do
160
+ before do
161
+ agent = create :agent
162
+ attributes = {
163
+ from: '310-456-7890',
164
+ to: '310-765-4321',
165
+ loan_id: 1,
166
+ from_id: agent.csr_id,
167
+ from_type: 'csr'
168
+ }
169
+ @existing_whitelist = Telephony.whitelist
170
+ Telephony.whitelist = [attributes[:from], attributes[:to]]
171
+ @conversation_count = Conversation.count
172
+ # Twilio doesn't allow you to buy new numbers using a test account
173
+ Telephony.provider.stub(:caller_id_for).and_return attributes[:from]
174
+
175
+ xhr :post, :create, attributes
176
+ end
177
+
178
+ after do
179
+ Telephony.whitelist = @existing_whitelist
180
+ end
181
+
182
+ it "creates a new conversation" do
183
+ Conversation.count.should == @conversation_count + 1
184
+ end
185
+
186
+ it 'creates a new call' do
187
+ Call.count.should == 2
188
+ end
189
+
190
+ it 'returns the conversation as JSON' do
191
+ conversation = Conversation.last
192
+ json = JSON response.body
193
+ json['id'].should == conversation.id
194
+ end
195
+ end
196
+
197
+ context "when an exception occurs" do
198
+ before do
199
+ agent = create :agent
200
+ attributes = {
201
+ from: '310-456-7890',
202
+ to: '310-765-4321',
203
+ loan_id: 1,
204
+ from_id: agent.csr_id,
205
+ from_type: 'csr'
206
+ }
207
+
208
+ Conversation
209
+ .stub(:begin!)
210
+ .and_raise(Telephony::Error::Connection.new("Call failed"))
211
+
212
+ xhr :post, :create, attributes
213
+ end
214
+
215
+ it 'return a 500 failure response' do
216
+ response.code.should == "500"
217
+ end
218
+
219
+ it 'returns an error message as JSON' do
220
+ json = JSON response.body
221
+ json.should include('errors')
222
+ errors = json['errors']
223
+ errors.should have(2).error
224
+ errors[1].should == 'Call failed'
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end