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

/actionpack/test/controller/live_stream_test.rb

https://github.com/Tho85/rails
Ruby | 316 lines | 257 code | 59 blank | 0 comment | 6 complexity | 4582390122e4f75c6d1ac7183d96a19f 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_controller
  128. raise 'Exception in controller'
  129. end
  130. def bad_request_error
  131. raise ActionController::BadRequest
  132. end
  133. def exception_in_exception_callback
  134. response.headers['Content-Type'] = 'text/event-stream'
  135. response.stream.on_error do
  136. raise 'We need to go deeper.'
  137. end
  138. response.stream.write params[:widget][:didnt_check_for_nil]
  139. end
  140. end
  141. tests TestController
  142. class TestResponse < Live::Response
  143. def recycle!
  144. initialize
  145. end
  146. end
  147. def build_response
  148. TestResponse.new
  149. end
  150. def assert_stream_closed
  151. assert response.stream.closed?, 'stream should be closed'
  152. end
  153. def capture_log_output
  154. output = StringIO.new
  155. old_logger, ActionController::Base.logger = ActionController::Base.logger, ActiveSupport::Logger.new(output)
  156. begin
  157. yield output
  158. ensure
  159. ActionController::Base.logger = old_logger
  160. end
  161. end
  162. def test_set_response!
  163. @controller.set_response!(@request)
  164. assert_kind_of(Live::Response, @controller.response)
  165. assert_equal @request, @controller.response.request
  166. end
  167. def test_write_to_stream
  168. @controller = TestController.new
  169. get :basic_stream
  170. assert_equal "helloworld", @response.body
  171. assert_equal 'text/event-stream', @response.headers['Content-Type']
  172. end
  173. def test_async_stream
  174. @controller.latch = ActiveSupport::Concurrency::Latch.new
  175. parts = ['hello', 'world']
  176. @controller.request = @request
  177. @controller.response = @response
  178. t = Thread.new(@response) { |resp|
  179. resp.stream.each do |part|
  180. assert_equal parts.shift, part
  181. ol = @controller.latch
  182. @controller.latch = ActiveSupport::Concurrency::Latch.new
  183. ol.release
  184. end
  185. }
  186. @controller.process :blocking_stream
  187. assert t.join(3), 'timeout expired before the thread terminated'
  188. end
  189. def test_thread_locals_get_copied
  190. @controller.tc = self
  191. Thread.current[:originating_thread] = Thread.current.object_id
  192. Thread.current[:setting] = 'aaron'
  193. get :thread_locals
  194. end
  195. def test_live_stream_default_header
  196. @controller.request = @request
  197. @controller.response = @response
  198. @controller.process :default_header
  199. _, headers, _ = @response.prepare!
  200. assert headers['Content-Type']
  201. end
  202. def test_render_text
  203. get :render_text
  204. assert_equal 'zomg', response.body
  205. assert_stream_closed
  206. end
  207. def test_exception_handling_html
  208. capture_log_output do |output|
  209. get :exception_in_view
  210. assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body
  211. assert_match 'Missing template test/doesntexist', output.rewind && output.read
  212. assert_stream_closed
  213. end
  214. end
  215. def test_exception_handling_plain_text
  216. capture_log_output do |output|
  217. get :exception_in_view, format: :json
  218. assert_equal '', response.body
  219. assert_match 'Missing template test/doesntexist', output.rewind && output.read
  220. assert_stream_closed
  221. end
  222. end
  223. def test_exception_callback
  224. capture_log_output do |output|
  225. get :exception_with_callback, format: 'text/event-stream'
  226. assert_equal %(data: "500 Internal Server Error"\n\n), response.body
  227. assert_match 'An exception occurred...', output.rewind && output.read
  228. assert_stream_closed
  229. end
  230. end
  231. def test_exception_in_controller_before_streaming
  232. response = get :exception_in_controller, format: 'text/event-stream'
  233. assert_equal 500, response.status
  234. end
  235. def test_bad_request_in_controller_before_streaming
  236. response = get :bad_request_error, format: 'text/event-stream'
  237. assert_equal 400, response.status
  238. end
  239. def test_exceptions_raised_handling_exceptions
  240. capture_log_output do |output|
  241. get :exception_in_exception_callback, format: 'text/event-stream'
  242. assert_equal '', response.body
  243. assert_match 'We need to go deeper', output.rewind && output.read
  244. assert_stream_closed
  245. end
  246. end
  247. def test_stale_without_etag
  248. get :with_stale
  249. assert_equal 200, @response.status.to_i
  250. end
  251. def test_stale_with_etag
  252. @request.if_none_match = Digest::MD5.hexdigest("123")
  253. get :with_stale
  254. assert_equal 304, @response.status.to_i
  255. end
  256. end
  257. end