spec/unit/server_spec.rb in hatetepe-0.4.1 vs spec/unit/server_spec.rb in hatetepe-0.5.0.pre

- old
+ new

@@ -1,355 +1,125 @@ +# -*- encoding: utf-8 -*- + require "spec_helper" require "hatetepe/server" -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) { 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, - :timeout => 0.0123 - } - } - - before do - server.stub :sockaddr => [42424, "127.0.42.1"] - @old_env, ENV["RACK_ENV"] = ENV["RACK_ENV"], "testing" +describe Hatetepe::Server, "(public API)" do + describe ".start(config, &app)" do + it "starts a server that listens on the supplied interface and port" + it "passes the config to incoming connections" + it "uses the passed block as app if any was passed" end - - after do - ENV["RACK_ENV"] = @old_env + + describe ".stop" do + it "waits for all requests to finish" + it "stops the server" end - - it "inherits from Hatetepe::Connection" do - server.should be_a(Hatetepe::Connection) + + describe ".stop!" do + it "immediately stops the server" 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 } - - Hatetepe::Server.start(config).should equal(server) + + describe "config[:app].call(env)" do + let :subject do + Object.new.tap do |s| + s.extend Hatetepe::Server + s.stub({ + :config => { + :host => "127.0.5.1", + :port => 3000, + :app => stub("app") + }, + :send_data => nil + }) + s.stub(:comm_inactivity_timeout=) + s.post_init + end end - end - - context "#initialize(config)" do - let(:server) { Hatetepe::Server.allocate } - - it "sets up the error stream" do - server.send :initialize, config - server.errors.should equal(errors) - server.config[:errors].should be_nil + + let :app do + subject.config[:app] end - - it "uses stderr as default error stream" do - config.delete :errors - server.send :initialize, config - server.errors.should equal($stderr) + + let :http_request do + [ + "POST /foo/bar?key=value HTTP/1.1", + "Host: themachine.local", + "Content-Length: 13", + "", + "Hello, world!" + ].join("\r\n") end - - it "assumes a default connection inactivity timeout of 1 seconds" do - server.send :initialize, {} - server.config[:timeout].should equal(1) + + let :http_response do + [ + "HTTP/1.1 403 Forbidden", + "Content-Type: text/plain", + "Transfer-Encoding: chunked", + "", + "b", + "Mmh, nöö.", + "0", + "", + "" + ].join("\r\n") end - end - - context "#post_init" do - let :server do - Hatetepe::Server.allocate.tap do |s| - s.send :initialize, config - s.stub :comm_inactivity_timeout= + + it "receives the Rack Env hash as parameter" do + app.should_receive :call do |env| + Rack::Lint.new(app).check_env(env) + env["REQUEST_METHOD"].should == "POST" + env["REQUEST_URI"].should == "/foo/bar?key=value" + env["HTTP_HOST"].should == "themachine.local" + env["rack.input"].read.should == "Hello, world!" + [ 200, {}, [] ] end + + subject.receive_data(http_request) end - - it "sets up the request queue" do - server.post_init - server.requests.should be_an(Array) - server.requests.should be_empty + + it "returns a response array that will be sent to the client" do + app.should_receive :call do |env| + [ 403, { "Content-Type" => "text/plain" }, [ "Mmh, nöö." ] ] + end + + sent = "" + subject.stub(:send_data) {|data| sent << data } + + subject.receive_data(http_request) + sent.should == http_response 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 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 - end - - it "closes the connection if parsing fails" do - server.parser.should_receive(:<<).and_raise(Hatetepe::ParserError) - 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 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") - } - errors.should_receive :flush - - 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) - e["rack.input"].source.should equal(server) - e["rack.errors"].should equal(server.errors) - e["rack.multithread"].should be_false - e["rack.multiprocess"].should be_false - 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 - end - - it "calls the app within a new Fiber" do - outer_fiber = Fiber.current - app.should_receive(:call) { - 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) { - [200, {"Key" => "value"}, Rack::STREAMING] - } - - before { - server.requests.unshift previous - app.stub(:call) {|e| response } - request.stub :succeed - server.builder.stub :response_line - server.builder.stub :headers - } - - it "deletes itself from env[] to prevent multiple calls" do - app.stub(:call) {|e| - e["stream.start"].call response - e.key?("stream.start").should be_false - [-1] - } - 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 - end - - context "env[stream.send].call(chunk)" do - it "passes data to the builder" do - app.stub(:call) {|e| - e["stream.send"].should == server.builder.method(:body_chunk) - [-1] - } - server.process - end - end - - context "env[stream.close].call" do - before { - server.builder.stub :complete - request.stub :succeed - } - - 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 "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] } - - 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 - 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] + + it "returns asynchronously" do + app.should_receive :call do |env| + EM::Synchrony.add_timer 0.5 do + response = [ 403, { "Content-Type" => "text/plain" }, [ "Mmh, nöö." ] ] + env["async.callback"].call(response) + end + [ -1, {}, [] ] end + + sent = "" + subject.stub(:send_data) {|data| sent << data } + + subject.receive_data(http_request) + EM::Synchrony.sleep(0.55) + sent.should == http_response end - - it "removes the request from the request queue" do - server.process - server.requests.should be_empty - end - - 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 + +describe Hatetepe::Server, "(EventMachine/sermipublic API)" do + describe "#initialize(config)" + + describe "#post_init" + + describe "#receive_data(data)" + + describe "#unbind(reason)" +end + +describe Hatetepe::Server, "(private API)" do + describe "#process_request(request)" + + describe "#send_response(response)" end