spec/unit/server_spec.rb in hatetepe-0.3.1 vs spec/unit/server_spec.rb in hatetepe-0.4.0

- old
+ new

@@ -3,34 +3,45 @@ describe Hatetepe::Server do let(:server) { Hatetepe::Server.allocate.tap {|s| s.send :initialize, config + s.stub :comm_inactivity_timeout= s.post_init s.requests << request } } - let(:request) { stub "request", :to_hash => env } - let(:env) { - { - "rack.input" => Hatetepe::Body.new - } - } + let(:request) { Hatetepe::Request.new :get, "/" } + let(:env) { request.to_h } let(:app) { stub "app" } let(:host) { "127.0.4.1" } let(:port) { 8081 } let(:errors) { stub "errors", :<< => nil, :flush => nil } let(:config) { { :app => app, :host => host, :port => port, - :errors => errors + :errors => errors, + :timeout => 0.0123 } } + before do + server.stub :sockaddr => [42424, "127.0.42.1"] + @old_env, ENV["RACK_ENV"] = ENV["RACK_ENV"], "testing" + end + + after do + ENV["RACK_ENV"] = @old_env + end + + it "inherits from Hatetepe::Connection" do + server.should be_a(Hatetepe::Connection) + end + context ".start(config)" do it "starts an EventMachine server" do args = [host, port, Hatetepe::Server, config] EM.should_receive(:start_server).with(*args) { server } @@ -38,58 +49,79 @@ end end context "#initialize(config)" do let(:server) { Hatetepe::Server.allocate } - let(:builder) { stub "app builder", :to_app => to_app } - let(:to_app) { stub "to_app" } - - it "builds the app" do - Rack::Builder.stub :new => builder - builder.should_receive(:use).with Hatetepe::Pipeline - builder.should_receive(:use).with Hatetepe::App - builder.should_receive(:use).with Hatetepe::Proxy - builder.should_receive(:run).with app - - server.send :initialize, config - server.app.should equal(to_app) - end it "sets up the error stream" do server.send :initialize, config server.errors.should equal(errors) + server.config[:errors].should be_nil end it "uses stderr as default error stream" do config.delete :errors server.send :initialize, config server.errors.should equal($stderr) end + + it "assumes a default connection inactivity timeout of 1 seconds" do + server.send :initialize, {} + server.config[:timeout].should equal(1) + end end context "#post_init" do - let(:server) { - Hatetepe::Server.allocate.tap {|s| s.post_init } - } + let :server do + Hatetepe::Server.allocate.tap do |s| + s.send :initialize, config + s.stub :comm_inactivity_timeout= + end + end it "sets up the request queue" do + server.post_init server.requests.should be_an(Array) server.requests.should be_empty end it "sets up the parser" do + server.post_init server.parser.should respond_to(:<<) server.parser.on_request[0].should == server.requests.method(:<<) end it "sets up the builder" do + server.post_init server.builder.on_write[0].should == server.method(:send_data) end + + it "builds the app" do + server.post_init + server.app.should be_a(Hatetepe::Server::Pipeline) + server.app.app.should be_a(Hatetepe::Server::App) + server.app.app.app.should be_a(Hatetepe::Server::KeepAlive) + server.app.app.app.app.should be_a(Hatetepe::Server::Proxy) + server.app.app.app.app.app.should equal(app) + end + + it "starts the connection inactivity tracking" do + server.should_receive(:comm_inactivity_timeout=).with 0.0123 + server.post_init + end + + it "enables request processing" do + server.post_init + server.processing_enabled.should be_true + end end context "#receive_data(data)" do - before { server.stub :close_connection_after_writing } + before do + ENV.delete "RACK_ENV" + server.stub :close_connection + end it "feeds data into the parser" do data = stub("data") server.parser.should_receive(:<<).with data server.receive_data data @@ -100,17 +132,33 @@ server.should_receive :close_connection server.receive_data "irrelevant data" end + it "re-raises parsing errors if RACK_ENV is testing" do + ENV["RACK_ENV"] = "testing" + server.parser.should_receive(:<<).and_raise Hatetepe::ParserError + + expect { + server.receive_data "irrelevant data" + }.to raise_error(Hatetepe::ParserError) + end + it "closes the connection when catching an exception" do - server.parser.should_receive(:<<).and_raise + server.parser.should_receive(:<<).and_raise Exception server.should_receive :close_connection_after_writing server.receive_data "" end + it "re-raises caught exceptions" do + ENV["RACK_ENV"] = "testing" + server.parser.should_receive(:<<).and_raise Exception + + expect { server.receive_data "" }.to raise_error(Exception) + end + it "logs caught exceptions" do server.parser.should_receive(:<<).and_raise "error message" errors.should_receive(:<<) {|str| str.should include("error message") } @@ -119,10 +167,15 @@ server.receive_data "" end end context "#process" do + before do + request.stub :to_h => env + app.stub :call => [-1] + end + it "puts useful stuff into env[]" do app.should_receive(:call) {|e| e.should equal(env) e["rack.url_scheme"].should == "http" e["hatetepe.connection"].should equal(server) @@ -133,10 +186,13 @@ e["rack.run_once"].should be_false e["SERVER_NAME"].should == host e["SERVER_NAME"].should_not equal(host) e["SERVER_PORT"].should == String(port) + e["REMOTE_ADDR"].should == server.remote_address + e["REMOTE_ADDR"].should_not equal(server.remote_address) + e["REMOTE_PORT"].should == String(server.remote_port) e["HTTP_HOST"].should == "#{host}:#{port}" [-1] } server.process @@ -148,10 +204,36 @@ Fiber.current.should_not equal(outer_fiber) [-1] } server.process end + + it "is a no-op if processing is disabled" do + server.processing_enabled = false + app.should_not_receive :call + server.process + end + + let(:another_request) { Hatetepe::Request.new :get, "/asdf" } + + it "disables the connection timeout until the last request is finished" do + server.requests << another_request + + server.should_receive(:comm_inactivity_timeout=).with 0 + server.process + + server.should_not_receive(:comm_inactivity_timeout=).with config[:timeout] + server.requests.delete request + request.succeed + + server.rspec_verify + server.rspec_reset + + server.should_receive(:comm_inactivity_timeout=).with config[:timeout] + server.requests.delete another_request + another_request.succeed + end end context "env[stream.start].call(response)" do let(:previous) { EM::DefaultDeferrable.new } let(:response) { @@ -174,26 +256,15 @@ } previous.succeed server.process end + # TODO this should be moved to a Server::Pipeline spec it "waits for the previous request's response to finish" do server.builder.should_not_receive :response server.process end - - it "initiates the response" do - server.builder.should_receive(:response_line) {|code| - code.should equal(response[0]) - } - server.builder.should_receive(:headers) {|headers| - headers["Key"].should equal(response[1]["Key"]) - headers["Server"].should == "hatetepe/#{Hatetepe::VERSION}" - } - previous.succeed - server.process - end end context "env[stream.send].call(chunk)" do it "passes data to the builder" do app.stub(:call) {|e| @@ -204,58 +275,81 @@ end end context "env[stream.close].call" do before { - server.stub :close_connection server.builder.stub :complete request.stub :succeed } - it "completes the response" do - server.builder.should_receive :complete + it "leaves the connection open" do + server.should_not_receive :close_connection app.stub(:call) {|e| + server.requests << stub("another request") e["stream.close"].call [-1] } server.process end - it "succeeds the request" do - request.should_receive :succeed + it "deletes itself and stream.send from env[] to prevent multiple calls" do app.stub(:call) {|e| e["stream.close"].call + e.key?("stream.send").should be_false + e.key?("stream.close").should be_false [-1] } server.process end + end + + context "#start_response(response)" do + let(:previous) { EM::DefaultDeferrable.new } + let(:response) { [200, {"Key" => "value"}, Rack::STREAMING] } - it "leaves the connection open" do - server.should_not_receive :close_connection - app.stub(:call) {|e| - server.requests << stub("another request") - e["stream.close"].call - [-1] + before { + server.requests.unshift previous + app.stub(:call) {|e| response } + request.stub :succeed + server.builder.stub :response_line + server.builder.stub :headers + } + + it "initiates the response" do + server.builder.should_receive(:response_line) {|code| + code.should equal(response[0]) } + server.builder.should_receive(:headers) {|headers| + headers["Key"].should equal(response[1]["Key"]) + headers["Server"].should == "hatetepe/#{Hatetepe::VERSION}" + } + previous.succeed server.process end - - it "closes the connection if there are no more requests" do - server.should_receive(:close_connection).with true - app.stub(:call) {|e| + end + + context "#close_response(request)" do + before do + server.builder.stub :complete + request.stub :succeed + app.stub :call do |e| e["stream.close"].call [-1] - } + end + end + + it "removes the request from the request queue" do server.process + server.requests.should be_empty end - it "deletes itself and stream.send from env[] to prevent multiple calls" do - app.stub(:call) {|e| - e["stream.close"].call - e.key?("stream.send").should be_false - e.key?("stream.close").should be_false - [-1] - } + it "completes the response" do + server.builder.should_receive :complete + server.process + end + + it "succeeds the request" do + request.should_receive :succeed server.process end end end