# frozen_string_literal: true RSpec.describe Hijack do class Hijack::Tester < ApplicationController attr_reader :io include Hijack def initialize(env = {}) @io = StringIO.new env.merge!("rack.hijack" => lambda { @io }, "rack.input" => StringIO.new) self.request = ActionController::TestRequest.new(env, nil, nil) # we need this for the 418 self.response = ActionDispatch::Response.new end def hijack_test(&blk) hijack(&blk) end end let :tester do Hijack::Tester.new end describe "Request Tracker integration" do let :logger do lambda do |env, data| @calls += 1 @status = data[:status] @total = data[:timing][:total_duration] end end before do Middleware::RequestTracker.register_detailed_request_logger logger @calls = 0 end after { Middleware::RequestTracker.unregister_detailed_request_logger logger } it "can properly track execution" do app = lambda do |env| tester = Hijack::Tester.new(env) tester.hijack_test { render body: "hello", status: 201 } end env = create_request_env(path: "/") middleware = Middleware::RequestTracker.new(app) middleware.call(env) expect(@calls).to eq(1) expect(@status).to eq(201) end end it "dupes the request params and env" do orig_req = tester.request copy_req = nil tester.hijack_test do copy_req = request render body: "hello world", status: 200 end expect(copy_req.object_id).not_to eq(orig_req.object_id) end it "handles cors" do SiteSetting.cors_origins = "www.rainbows.com" app = lambda do |env| tester = Hijack::Tester.new(env) tester.hijack_test { render body: "hello", status: 201 } expect(tester.io.string).to include("Access-Control-Allow-Origin: www.rainbows.com") end env = {} middleware = Discourse::Cors.new(app) middleware.call(env) # it can do pre-flight env = { "REQUEST_METHOD" => "OPTIONS", "HTTP_ACCESS_CONTROL_REQUEST_METHOD" => "GET" } status, headers, _body = middleware.call(env) expect(status).to eq(200) expected = { "Access-Control-Allow-Origin" => "www.rainbows.com", "Access-Control-Allow-Headers" => "Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Present, User-Api-Key, User-Api-Client-Id, Authorization", "Access-Control-Allow-Credentials" => "true", "Access-Control-Allow-Methods" => "POST, PUT, GET, OPTIONS, DELETE", "Access-Control-Max-Age" => "7200", } expect(headers).to eq(expected) end it "removes trailing slash in cors origin" do GlobalSetting.stubs(:enable_cors).returns(true) GlobalSetting.stubs(:cors_origin).returns("https://www.rainbows.com/") app = lambda do |env| tester = Hijack::Tester.new(env) tester.hijack_test { render body: "hello", status: 201 } expect(tester.io.string).to include("Access-Control-Allow-Origin: https://www.rainbows.com") end env = {} middleware = Discourse::Cors.new(app) middleware.call(env) # it can do pre-flight env = { "REQUEST_METHOD" => "OPTIONS", "HTTP_ACCESS_CONTROL_REQUEST_METHOD" => "GET" } status, headers, _body = middleware.call(env) expect(status).to eq(200) expected = { "Access-Control-Allow-Origin" => "https://www.rainbows.com", "Access-Control-Allow-Headers" => "Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Present, User-Api-Key, User-Api-Client-Id, Authorization", "Access-Control-Allow-Credentials" => "true", "Access-Control-Allow-Methods" => "POST, PUT, GET, OPTIONS, DELETE", "Access-Control-Max-Age" => "7200", } expect(headers).to eq(expected) end it "handles transfers headers" do tester.response.headers["Hello-World"] = "sam" tester.hijack_test do expires_in 1.year render body: "hello world", status: 402 end expect(tester.io.string).to include("Hello-World: sam") end it "handles expires_in" do tester.hijack_test do expires_in 1.year render body: "hello world", status: 402 end expect(tester.io.string).to include("max-age=31556952") end it "renders non 200 status if asked for" do tester.hijack_test { render body: "hello world", status: 402 } expect(tester.io.string).to include("402") expect(tester.io.string).to include("world") end it "handles send_file correctly" do tester.hijack_test { send_file __FILE__, disposition: nil } expect(tester.io.string).to start_with("HTTP/1.1 200") end it "renders a redirect correctly" do Process.stubs(:clock_gettime).returns(1.0) tester.hijack_test do Process.stubs(:clock_gettime).returns(2.0) redirect_to "http://awesome.com", allow_other_host: true end result = "HTTP/1.1 302 Found\r\nLocation: http://awesome.com\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 84\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\nYou are being redirected." expect(tester.io.string).to eq(result) end it "renders stuff correctly if is empty" do Process.stubs(:clock_gettime).returns(1.0) tester.hijack_test do Process.stubs(:clock_gettime).returns(2.0) render body: nil end result = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 0\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\n" expect(tester.io.string).to eq(result) end it "renders stuff correctly if it works" do Process.stubs(:clock_gettime).returns(1.0) tester.hijack_test do Process.stubs(:clock_gettime).returns(2.0) render plain: "hello world" end result = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 11\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\nhello world" expect(tester.io.string).to eq(result) end it "returns 500 by default" do Process.stubs(:clock_gettime).returns(1.0) tester.hijack_test expected = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 0\r\nConnection: close\r\nX-Runtime: 0.000000\r\n\r\n" expect(tester.io.string).to eq(expected) end it "does not run the block if io is closed" do tester.io.close ran = false tester.hijack_test { ran = true } expect(ran).to eq(false) end it "handles the queue being full" do Scheduler::Defer.stubs(:later).raises(WorkQueue::WorkQueueFull.new) tester.hijack_test {} expect(tester.response.status).to eq(503) end end