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