require 'test_helper' module WebConsole class MiddlewareTest < ActionDispatch::IntegrationTest class Application def initialize(options = {}) @response_content_type = options[:response_content_type] || Mime[:html] end def call(env) [ status, headers, body ] end private def status 500 end def headers { 'Content-Type' => "#{@response_content_type}; charset=utf-8" } end def body Array(<<-HTML.strip_heredoc) Hello world

Hello world

HTML end end setup do Thread.current[:__web_console_exception] = nil Thread.current[:__web_console_binding] = nil Rails.stubs(:root).returns Pathname(__FILE__).parent Request.stubs(:whitelisted_ips).returns(IPAddr.new('0.0.0.0/0')) Middleware.mount_point = '' @app = Middleware.new(Application.new) end test 'render console in an html application from web_console.binding' do Thread.current[:__web_console_binding] = binding get '/', params: nil assert_select '#console' end test 'render console in an html application from web_console.exception' do Thread.current[:__web_console_exception] = raise_exception get '/', params: nil assert_select 'body > #console' end test 'render error_page.js from web_console.exception' do Thread.current[:__web_console_exception] = raise_exception get '/', params: nil assert_select 'body > script[data-template=error_page]' end test 'render console if response format is HTML' do Thread.current[:__web_console_binding] = binding @app = Middleware.new(Application.new(response_content_type: Mime[:html])) get '/', params: nil assert_select '#console' end test 'does not render console if response format is not HTML' do Thread.current[:__web_console_binding] = binding @app = Middleware.new(Application.new(response_content_type: Mime[:json])) get '/', params: nil assert_select '#console', 0 end test 'returns X-Web-Console-Session-Id as response header' do Thread.current[:__web_console_binding] = binding get '/', params: nil session_id = response.headers["X-Web-Console-Session-Id"] assert_not Session.find(session_id).nil? end test "doesn't render console in non html response" do Thread.current[:__web_console_binding] = binding @app = Middleware.new(Application.new(response_content_type: Mime[:json])) get '/', params: nil assert_select '#console', 0 end test "doesn't render console from non whitelisted IP" do Thread.current[:__web_console_binding] = binding Request.stubs(:whitelisted_ips).returns(IPAddr.new('127.0.0.1')) silence(:stderr) do get '/', params: nil, headers: { 'REMOTE_ADDR' => '1.1.1.1' } end assert_select '#console', 0 end test "doesn't render console without a web_console.binding or web_console.exception" do get '/', params: nil assert_select '#console', 0 end test 'can evaluate code and return it as a JSON' do session, line = Session.new([binding]), __LINE__ Session.stubs(:from).returns(session) get '/', params: nil put "/repl_sessions/#{session.id}", xhr: true, params: { input: '__LINE__' } assert_equal("=> #{line}\n", JSON.parse(response.body)["output"]) end test 'can switch bindings on error pages' do session = Session.new(raise_exception.bindings) Session.stubs(:from).returns(session) get '/', params: nil post "/repl_sessions/#{session.id}/trace", xhr: true, params: { frame_id: 1 } assert_equal({ ok: true }.to_json, response.body) end test 'can be changed mount point' do Middleware.mount_point = '/customized/path' session, line = Session.new([binding]), __LINE__ put "/customized/path/repl_sessions/#{session.id}", params: { input: '__LINE__' }, xhr: true assert_equal("=> #{line}\n", JSON.parse(response.body)["output"]) end test 'can return context information by passing a context param' do hello = 'world' session = Session.new([binding]) Session.stubs(:from).returns(session) get '/' put "/repl_sessions/#{session.id}", xhr: true, params: { context: '' } assert_includes(JSON.parse(response.body)["context"], local_variables.map(&:to_s)) end test 'unavailable sessions respond to the user with a message' do put '/repl_sessions/no_such_session', xhr: true, params: { input: '__LINE__' } assert_equal(404, response.status) end test 'unavailable sessions can occur on binding switch' do post "/repl_sessions/no_such_session/trace", xhr: true, params: { frame_id: 1 } assert_equal(404, response.status) end test "doesn't accept request for old version and return 406" do put "/repl_sessions/no_such_session", xhr: true, params: { input: "__LINE__" }, headers: {"HTTP_ACCEPT" => "application/vnd.web-console.v0"} assert_equal(406, response.status) end test 'reraises application errors' do @app = proc { raise } assert_raises(RuntimeError) { get '/' } end private # Override the put and post testing helper of ActionDispatch to customize http headers def put(http_method, path, *args) update_path_args(path) super end def post(http_method, path, *args) update_path_args(path) super end def update_path_args(path) unless path[:headers] path.merge!(headers: { 'HTTP_ACCEPT' => Mime[:web_console_v2] }) end end def raise_exception raise rescue => exc exc end end end