2012-07-29 20:26:07 -04:00
|
|
|
require 'abstract_unit'
|
2012-07-29 22:23:19 -04:00
|
|
|
require 'active_support/concurrency/latch'
|
2014-02-28 18:22:35 -05:00
|
|
|
Thread.abort_on_exception = true
|
2012-07-29 20:26:07 -04:00
|
|
|
|
|
|
|
module ActionController
|
2013-07-14 18:48:57 -04:00
|
|
|
class SSETest < ActionController::TestCase
|
|
|
|
class SSETestController < ActionController::Base
|
|
|
|
include ActionController::Live
|
|
|
|
|
|
|
|
def basic_sse
|
|
|
|
response.headers['Content-Type'] = 'text/event-stream'
|
|
|
|
sse = SSE.new(response.stream)
|
|
|
|
sse.write("{\"name\":\"John\"}")
|
|
|
|
sse.write({ name: "Ryan" })
|
|
|
|
ensure
|
|
|
|
sse.close
|
|
|
|
end
|
|
|
|
|
|
|
|
def sse_with_event
|
|
|
|
sse = SSE.new(response.stream, event: "send-name")
|
|
|
|
sse.write("{\"name\":\"John\"}")
|
|
|
|
sse.write({ name: "Ryan" })
|
|
|
|
ensure
|
|
|
|
sse.close
|
|
|
|
end
|
|
|
|
|
|
|
|
def sse_with_retry
|
|
|
|
sse = SSE.new(response.stream, retry: 1000)
|
|
|
|
sse.write("{\"name\":\"John\"}")
|
|
|
|
sse.write({ name: "Ryan" }, retry: 1500)
|
|
|
|
ensure
|
|
|
|
sse.close
|
|
|
|
end
|
|
|
|
|
|
|
|
def sse_with_id
|
|
|
|
sse = SSE.new(response.stream)
|
|
|
|
sse.write("{\"name\":\"John\"}", id: 1)
|
|
|
|
sse.write({ name: "Ryan" }, id: 2)
|
|
|
|
ensure
|
|
|
|
sse.close
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
tests SSETestController
|
|
|
|
|
|
|
|
def wait_for_response_stream_close
|
2014-03-12 20:40:08 -04:00
|
|
|
response.body
|
2013-07-14 18:48:57 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_basic_sse
|
|
|
|
get :basic_sse
|
|
|
|
|
|
|
|
wait_for_response_stream_close
|
|
|
|
assert_match(/data: {\"name\":\"John\"}/, response.body)
|
|
|
|
assert_match(/data: {\"name\":\"Ryan\"}/, response.body)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_sse_with_event_name
|
|
|
|
get :sse_with_event
|
|
|
|
|
|
|
|
wait_for_response_stream_close
|
|
|
|
assert_match(/data: {\"name\":\"John\"}/, response.body)
|
|
|
|
assert_match(/data: {\"name\":\"Ryan\"}/, response.body)
|
|
|
|
assert_match(/event: send-name/, response.body)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_sse_with_retry
|
|
|
|
get :sse_with_retry
|
|
|
|
|
|
|
|
wait_for_response_stream_close
|
|
|
|
first_response, second_response = response.body.split("\n\n")
|
|
|
|
assert_match(/data: {\"name\":\"John\"}/, first_response)
|
|
|
|
assert_match(/retry: 1000/, first_response)
|
|
|
|
|
|
|
|
assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
|
|
|
|
assert_match(/retry: 1500/, second_response)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_sse_with_id
|
|
|
|
get :sse_with_id
|
|
|
|
|
|
|
|
wait_for_response_stream_close
|
|
|
|
first_response, second_response = response.body.split("\n\n")
|
|
|
|
assert_match(/data: {\"name\":\"John\"}/, first_response)
|
|
|
|
assert_match(/id: 1/, first_response)
|
|
|
|
|
|
|
|
assert_match(/data: {\"name\":\"Ryan\"}/, second_response)
|
|
|
|
assert_match(/id: 2/, second_response)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-07-29 22:23:19 -04:00
|
|
|
class LiveStreamTest < ActionController::TestCase
|
2014-02-28 18:39:08 -05:00
|
|
|
class Exception < StandardError
|
|
|
|
end
|
|
|
|
|
2012-07-29 20:26:07 -04:00
|
|
|
class TestController < ActionController::Base
|
2012-07-29 22:23:19 -04:00
|
|
|
include ActionController::Live
|
|
|
|
|
|
|
|
attr_accessor :latch, :tc
|
|
|
|
|
2012-07-29 20:26:07 -04:00
|
|
|
def self.controller_path
|
|
|
|
'test'
|
|
|
|
end
|
|
|
|
|
2014-03-12 19:07:04 -04:00
|
|
|
def set_cookie
|
|
|
|
cookies[:hello] = "world"
|
|
|
|
response.stream.write "hello world"
|
|
|
|
response.close
|
|
|
|
end
|
|
|
|
|
2012-07-29 23:33:16 -04:00
|
|
|
def render_text
|
|
|
|
render :text => 'zomg'
|
|
|
|
end
|
|
|
|
|
2012-07-29 23:26:40 -04:00
|
|
|
def default_header
|
|
|
|
response.stream.write "<html><body>hi</body></html>"
|
|
|
|
response.stream.close
|
|
|
|
end
|
|
|
|
|
2012-07-29 20:26:07 -04:00
|
|
|
def basic_stream
|
2012-07-29 22:23:19 -04:00
|
|
|
response.headers['Content-Type'] = 'text/event-stream'
|
|
|
|
%w{ hello world }.each do |word|
|
|
|
|
response.stream.write word
|
|
|
|
end
|
|
|
|
response.stream.close
|
|
|
|
end
|
|
|
|
|
|
|
|
def blocking_stream
|
|
|
|
response.headers['Content-Type'] = 'text/event-stream'
|
|
|
|
%w{ hello world }.each do |word|
|
|
|
|
response.stream.write word
|
|
|
|
latch.await
|
|
|
|
end
|
|
|
|
response.stream.close
|
|
|
|
end
|
|
|
|
|
|
|
|
def thread_locals
|
|
|
|
tc.assert_equal 'aaron', Thread.current[:setting]
|
2012-12-28 18:49:41 -05:00
|
|
|
tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread]
|
2012-07-29 22:23:19 -04:00
|
|
|
|
|
|
|
response.headers['Content-Type'] = 'text/event-stream'
|
2012-07-29 20:26:07 -04:00
|
|
|
%w{ hello world }.each do |word|
|
|
|
|
response.stream.write word
|
|
|
|
end
|
|
|
|
response.stream.close
|
|
|
|
end
|
2013-03-14 04:13:10 -04:00
|
|
|
|
|
|
|
def with_stale
|
|
|
|
render :text => 'stale' if stale?(:etag => "123")
|
|
|
|
end
|
2013-03-07 17:49:03 -05:00
|
|
|
|
|
|
|
def exception_in_view
|
|
|
|
render 'doesntexist'
|
|
|
|
end
|
|
|
|
|
2014-02-17 21:41:21 -05:00
|
|
|
def exception_in_view_after_commit
|
|
|
|
response.stream.write ""
|
|
|
|
render 'doesntexist'
|
|
|
|
end
|
|
|
|
|
2013-03-07 17:49:03 -05:00
|
|
|
def exception_with_callback
|
|
|
|
response.headers['Content-Type'] = 'text/event-stream'
|
|
|
|
|
|
|
|
response.stream.on_error do
|
|
|
|
response.stream.write %(data: "500 Internal Server Error"\n\n)
|
|
|
|
response.stream.close
|
|
|
|
end
|
|
|
|
|
2014-02-28 18:39:08 -05:00
|
|
|
response.stream.write "" # make sure the response is committed
|
2013-03-07 17:49:03 -05:00
|
|
|
raise 'An exception occurred...'
|
|
|
|
end
|
|
|
|
|
2014-02-15 16:05:00 -05:00
|
|
|
def exception_in_controller
|
2014-02-28 18:39:08 -05:00
|
|
|
raise Exception, 'Exception in controller'
|
2014-02-15 16:05:00 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def bad_request_error
|
|
|
|
raise ActionController::BadRequest
|
|
|
|
end
|
|
|
|
|
2013-03-07 17:49:03 -05:00
|
|
|
def exception_in_exception_callback
|
|
|
|
response.headers['Content-Type'] = 'text/event-stream'
|
|
|
|
response.stream.on_error do
|
|
|
|
raise 'We need to go deeper.'
|
|
|
|
end
|
2014-02-28 18:39:08 -05:00
|
|
|
response.stream.write ''
|
2013-03-07 17:49:03 -05:00
|
|
|
response.stream.write params[:widget][:didnt_check_for_nil]
|
|
|
|
end
|
2012-07-29 20:26:07 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
tests TestController
|
|
|
|
|
2013-03-07 17:49:03 -05:00
|
|
|
def assert_stream_closed
|
|
|
|
assert response.stream.closed?, 'stream should be closed'
|
2014-03-12 20:40:08 -04:00
|
|
|
assert response.sent?, 'stream should be sent'
|
2013-03-07 17:49:03 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def capture_log_output
|
|
|
|
output = StringIO.new
|
|
|
|
old_logger, ActionController::Base.logger = ActionController::Base.logger, ActiveSupport::Logger.new(output)
|
|
|
|
|
|
|
|
begin
|
|
|
|
yield output
|
|
|
|
ensure
|
|
|
|
ActionController::Base.logger = old_logger
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-03-12 19:07:04 -04:00
|
|
|
def test_set_cookie
|
|
|
|
@controller = TestController.new
|
|
|
|
get :set_cookie
|
|
|
|
assert_equal({'hello' => 'world'}, @response.cookies)
|
|
|
|
assert_equal "hello world", @response.body
|
|
|
|
end
|
|
|
|
|
2012-07-29 22:46:36 -04:00
|
|
|
def test_set_response!
|
|
|
|
@controller.set_response!(@request)
|
|
|
|
assert_kind_of(Live::Response, @controller.response)
|
|
|
|
assert_equal @request, @controller.response.request
|
|
|
|
end
|
|
|
|
|
2012-07-29 20:26:07 -04:00
|
|
|
def test_write_to_stream
|
2012-07-29 22:23:19 -04:00
|
|
|
@controller = TestController.new
|
2012-07-29 20:26:07 -04:00
|
|
|
get :basic_stream
|
2012-07-29 22:23:19 -04:00
|
|
|
assert_equal "helloworld", @response.body
|
|
|
|
assert_equal 'text/event-stream', @response.headers['Content-Type']
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_async_stream
|
|
|
|
@controller.latch = ActiveSupport::Concurrency::Latch.new
|
|
|
|
parts = ['hello', 'world']
|
|
|
|
|
|
|
|
@controller.request = @request
|
|
|
|
@controller.response = @response
|
|
|
|
|
|
|
|
t = Thread.new(@response) { |resp|
|
2014-03-12 20:40:08 -04:00
|
|
|
resp.await_commit
|
2012-07-29 22:23:19 -04:00
|
|
|
resp.stream.each do |part|
|
|
|
|
assert_equal parts.shift, part
|
|
|
|
ol = @controller.latch
|
|
|
|
@controller.latch = ActiveSupport::Concurrency::Latch.new
|
|
|
|
ol.release
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
@controller.process :blocking_stream
|
|
|
|
|
2013-08-02 10:57:28 -04:00
|
|
|
assert t.join(3), 'timeout expired before the thread terminated'
|
2012-07-29 22:23:19 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_thread_locals_get_copied
|
|
|
|
@controller.tc = self
|
|
|
|
Thread.current[:originating_thread] = Thread.current.object_id
|
|
|
|
Thread.current[:setting] = 'aaron'
|
|
|
|
|
|
|
|
get :thread_locals
|
2012-07-29 20:26:07 -04:00
|
|
|
end
|
2012-07-29 23:26:40 -04:00
|
|
|
|
|
|
|
def test_live_stream_default_header
|
|
|
|
@controller.request = @request
|
|
|
|
@controller.response = @response
|
|
|
|
@controller.process :default_header
|
|
|
|
_, headers, _ = @response.prepare!
|
|
|
|
assert headers['Content-Type']
|
|
|
|
end
|
2012-07-29 23:33:16 -04:00
|
|
|
|
|
|
|
def test_render_text
|
|
|
|
get :render_text
|
2012-08-02 18:04:36 -04:00
|
|
|
assert_equal 'zomg', response.body
|
2013-03-07 17:49:03 -05:00
|
|
|
assert_stream_closed
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_exception_handling_html
|
2014-02-28 18:39:08 -05:00
|
|
|
assert_raises(ActionView::MissingTemplate) do
|
2013-03-07 17:49:03 -05:00
|
|
|
get :exception_in_view
|
|
|
|
end
|
2014-02-17 21:41:21 -05:00
|
|
|
|
|
|
|
capture_log_output do |output|
|
|
|
|
get :exception_in_view_after_commit
|
|
|
|
assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body
|
|
|
|
assert_match 'Missing template test/doesntexist', output.rewind && output.read
|
|
|
|
assert_stream_closed
|
|
|
|
end
|
2014-03-12 20:40:08 -04:00
|
|
|
assert response.body
|
2014-02-28 18:39:08 -05:00
|
|
|
assert_stream_closed
|
2013-03-07 17:49:03 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_exception_handling_plain_text
|
2014-02-28 18:39:08 -05:00
|
|
|
assert_raises(ActionView::MissingTemplate) do
|
2013-03-07 17:49:03 -05:00
|
|
|
get :exception_in_view, format: :json
|
|
|
|
end
|
2014-02-17 21:41:21 -05:00
|
|
|
|
|
|
|
capture_log_output do |output|
|
|
|
|
get :exception_in_view_after_commit, format: :json
|
|
|
|
assert_equal '', response.body
|
|
|
|
assert_match 'Missing template test/doesntexist', output.rewind && output.read
|
|
|
|
assert_stream_closed
|
|
|
|
end
|
2013-03-07 17:49:03 -05:00
|
|
|
end
|
|
|
|
|
2014-02-28 18:39:08 -05:00
|
|
|
def test_exception_callback_when_committed
|
2013-03-07 17:49:03 -05:00
|
|
|
capture_log_output do |output|
|
|
|
|
get :exception_with_callback, format: 'text/event-stream'
|
|
|
|
assert_equal %(data: "500 Internal Server Error"\n\n), response.body
|
|
|
|
assert_match 'An exception occurred...', output.rewind && output.read
|
|
|
|
assert_stream_closed
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-02-15 16:05:00 -05:00
|
|
|
def test_exception_in_controller_before_streaming
|
2014-02-28 18:39:08 -05:00
|
|
|
assert_raises(ActionController::LiveStreamTest::Exception) do
|
|
|
|
get :exception_in_controller, format: 'text/event-stream'
|
|
|
|
end
|
2014-02-15 16:05:00 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_bad_request_in_controller_before_streaming
|
2014-02-28 18:39:08 -05:00
|
|
|
assert_raises(ActionController::BadRequest) do
|
|
|
|
get :bad_request_error, format: 'text/event-stream'
|
|
|
|
end
|
2014-02-15 16:05:00 -05:00
|
|
|
end
|
|
|
|
|
2014-02-28 18:39:08 -05:00
|
|
|
def test_exceptions_raised_handling_exceptions_and_committed
|
2013-03-07 17:49:03 -05:00
|
|
|
capture_log_output do |output|
|
|
|
|
get :exception_in_exception_callback, format: 'text/event-stream'
|
|
|
|
assert_equal '', response.body
|
|
|
|
assert_match 'We need to go deeper', output.rewind && output.read
|
|
|
|
assert_stream_closed
|
|
|
|
end
|
2012-07-29 23:33:16 -04:00
|
|
|
end
|
2013-03-14 04:13:10 -04:00
|
|
|
|
|
|
|
def test_stale_without_etag
|
|
|
|
get :with_stale
|
|
|
|
assert_equal 200, @response.status.to_i
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_stale_with_etag
|
|
|
|
@request.if_none_match = Digest::MD5.hexdigest("123")
|
|
|
|
get :with_stale
|
|
|
|
assert_equal 304, @response.status.to_i
|
|
|
|
end
|
2012-07-29 20:26:07 -04:00
|
|
|
end
|
2014-02-28 14:57:15 -05:00
|
|
|
|
|
|
|
class BufferTest < ActionController::TestCase
|
|
|
|
def test_nil_callback
|
|
|
|
buf = ActionController::Live::Buffer.new nil
|
|
|
|
assert buf.call_on_error
|
|
|
|
end
|
|
|
|
end
|
2012-07-29 20:26:07 -04:00
|
|
|
end
|