PageRenderTime 63ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/actionpack/test/controller/live_stream_test.rb

https://github.com/mitchel456/rails
Ruby | 298 lines | 243 code | 55 blank | 0 comment | 6 complexity | 2b88c21e72a69a8e21195d087125d92c MD5 | raw file
  1. require 'abstract_unit'
  2. require 'active_support/concurrency/latch'
  3. module ActionController
  4. class SSETest < ActionController::TestCase
  5. class SSETestController < ActionController::Base
  6. include ActionController::Live
  7. def basic_sse
  8. response.headers['Content-Type'] = 'text/event-stream'
  9. sse = SSE.new(response.stream)
  10. sse.write("{\"name\":\"John\"}")
  11. sse.write({ name: "Ryan" })
  12. ensure
  13. sse.close
  14. end
  15. def sse_with_event
  16. sse = SSE.new(response.stream, event: "send-name")
  17. sse.write("{\"name\":\"John\"}")
  18. sse.write({ name: "Ryan" })
  19. ensure
  20. sse.close
  21. end
  22. def sse_with_retry
  23. sse = SSE.new(response.stream, retry: 1000)
  24. sse.write("{\"name\":\"John\"}")
  25. sse.write({ name: "Ryan" }, retry: 1500)
  26. ensure
  27. sse.close
  28. end
  29. def sse_with_id
  30. sse = SSE.new(response.stream)
  31. sse.write("{\"name\":\"John\"}", id: 1)
  32. sse.write({ name: "Ryan" }, id: 2)
  33. ensure
  34. sse.close
  35. end
  36. end
  37. tests SSETestController
  38. def wait_for_response_stream_close
  39. while !response.stream.closed?
  40. sleep 0.01
  41. end
  42. end
  43. def test_basic_sse
  44. get :basic_sse
  45. wait_for_response_stream_close
  46. assert_match(/data: {\"name\":\"John\"}/, response.body)
  47. assert_match(/data: {\"name\":\"Ryan\"}/, response.body)
  48. end
  49. def test_sse_with_event_name
  50. get :sse_with_event
  51. wait_for_response_stream_close
  52. assert_match(/data: {\"name\":\"John\"}/, response.body)
  53. assert_match(/data: {\"name\":\"Ryan\"}/, response.body)
  54. assert_match(/event: send-name/, response.body)
  55. end
  56. def test_sse_with_retry
  57. get :sse_with_retry
  58. wait_for_response_stream_close
  59. first_response, second_response = response.body.split("\n\n")
  60. assert_match(/data: {\"name\":\"John\"}/, first_response)
  61. assert_match(/retry: 1000/, first_response)
  62. assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
  63. assert_match(/retry: 1500/, second_response)
  64. end
  65. def test_sse_with_id
  66. get :sse_with_id
  67. wait_for_response_stream_close
  68. first_response, second_response = response.body.split("\n\n")
  69. assert_match(/data: {\"name\":\"John\"}/, first_response)
  70. assert_match(/id: 1/, first_response)
  71. assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
  72. assert_match(/id: 2/, second_response)
  73. end
  74. end
  75. class LiveStreamTest < ActionController::TestCase
  76. class TestController < ActionController::Base
  77. include ActionController::Live
  78. attr_accessor :latch, :tc
  79. def self.controller_path
  80. 'test'
  81. end
  82. def render_text
  83. render :text => 'zomg'
  84. end
  85. def default_header
  86. response.stream.write "<html><body>hi</body></html>"
  87. response.stream.close
  88. end
  89. def basic_stream
  90. response.headers['Content-Type'] = 'text/event-stream'
  91. %w{ hello world }.each do |word|
  92. response.stream.write word
  93. end
  94. response.stream.close
  95. end
  96. def blocking_stream
  97. response.headers['Content-Type'] = 'text/event-stream'
  98. %w{ hello world }.each do |word|
  99. response.stream.write word
  100. latch.await
  101. end
  102. response.stream.close
  103. end
  104. def thread_locals
  105. tc.assert_equal 'aaron', Thread.current[:setting]
  106. tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread]
  107. response.headers['Content-Type'] = 'text/event-stream'
  108. %w{ hello world }.each do |word|
  109. response.stream.write word
  110. end
  111. response.stream.close
  112. end
  113. def with_stale
  114. render :text => 'stale' if stale?(:etag => "123")
  115. end
  116. def exception_in_view
  117. render 'doesntexist'
  118. end
  119. def exception_with_callback
  120. response.headers['Content-Type'] = 'text/event-stream'
  121. response.stream.on_error do
  122. response.stream.write %(data: "500 Internal Server Error"\n\n)
  123. response.stream.close
  124. end
  125. raise 'An exception occurred...'
  126. end
  127. def exception_in_exception_callback
  128. response.headers['Content-Type'] = 'text/event-stream'
  129. response.stream.on_error do
  130. raise 'We need to go deeper.'
  131. end
  132. response.stream.write params[:widget][:didnt_check_for_nil]
  133. end
  134. end
  135. tests TestController
  136. class TestResponse < Live::Response
  137. def recycle!
  138. initialize
  139. end
  140. end
  141. def build_response
  142. TestResponse.new
  143. end
  144. def assert_stream_closed
  145. assert response.stream.closed?, 'stream should be closed'
  146. end
  147. def capture_log_output
  148. output = StringIO.new
  149. old_logger, ActionController::Base.logger = ActionController::Base.logger, ActiveSupport::Logger.new(output)
  150. begin
  151. yield output
  152. ensure
  153. ActionController::Base.logger = old_logger
  154. end
  155. end
  156. def test_set_response!
  157. @controller.set_response!(@request)
  158. assert_kind_of(Live::Response, @controller.response)
  159. assert_equal @request, @controller.response.request
  160. end
  161. def test_write_to_stream
  162. @controller = TestController.new
  163. get :basic_stream
  164. assert_equal "helloworld", @response.body
  165. assert_equal 'text/event-stream', @response.headers['Content-Type']
  166. end
  167. def test_async_stream
  168. @controller.latch = ActiveSupport::Concurrency::Latch.new
  169. parts = ['hello', 'world']
  170. @controller.request = @request
  171. @controller.response = @response
  172. t = Thread.new(@response) { |resp|
  173. resp.stream.each do |part|
  174. assert_equal parts.shift, part
  175. ol = @controller.latch
  176. @controller.latch = ActiveSupport::Concurrency::Latch.new
  177. ol.release
  178. end
  179. }
  180. @controller.process :blocking_stream
  181. assert t.join(3), 'timeout expired before the thread terminated'
  182. end
  183. def test_thread_locals_get_copied
  184. @controller.tc = self
  185. Thread.current[:originating_thread] = Thread.current.object_id
  186. Thread.current[:setting] = 'aaron'
  187. get :thread_locals
  188. end
  189. def test_live_stream_default_header
  190. @controller.request = @request
  191. @controller.response = @response
  192. @controller.process :default_header
  193. _, headers, _ = @response.prepare!
  194. assert headers['Content-Type']
  195. end
  196. def test_render_text
  197. get :render_text
  198. assert_equal 'zomg', response.body
  199. assert_stream_closed
  200. end
  201. def test_exception_handling_html
  202. capture_log_output do |output|
  203. get :exception_in_view
  204. assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body
  205. assert_match 'Missing template test/doesntexist', output.rewind && output.read
  206. assert_stream_closed
  207. end
  208. end
  209. def test_exception_handling_plain_text
  210. capture_log_output do |output|
  211. get :exception_in_view, format: :json
  212. assert_equal '', response.body
  213. assert_match 'Missing template test/doesntexist', output.rewind && output.read
  214. assert_stream_closed
  215. end
  216. end
  217. def test_exception_callback
  218. capture_log_output do |output|
  219. get :exception_with_callback, format: 'text/event-stream'
  220. assert_equal %(data: "500 Internal Server Error"\n\n), response.body
  221. assert_match 'An exception occurred...', output.rewind && output.read
  222. assert_stream_closed
  223. end
  224. end
  225. def test_exceptions_raised_handling_exceptions
  226. capture_log_output do |output|
  227. get :exception_in_exception_callback, format: 'text/event-stream'
  228. assert_equal '', response.body
  229. assert_match 'We need to go deeper', output.rewind && output.read
  230. assert_stream_closed
  231. end
  232. end
  233. def test_stale_without_etag
  234. get :with_stale
  235. assert_equal 200, @response.status.to_i
  236. end
  237. def test_stale_with_etag
  238. @request.if_none_match = Digest::MD5.hexdigest("123")
  239. get :with_stale
  240. assert_equal 304, @response.status.to_i
  241. end
  242. end
  243. end