require "spec_helper" require "sham_rack" require "sham_rack/patron" require "open-uri" require "restclient" require "mechanize" require "rack" RSpec.describe ShamRack do class NetHttpProhibited < StandardError; end before do allow_any_instance_of(Net::HTTP).to receive(:start) do raise NetHttpProhibited, "real network calls are not allowed" end end after(:each) do ShamRack.reset end describe "mounted Rack application" do before(:each) do ShamRack.at("www.greetings.com").mount(GreetingApp.new) end it "can be accessed using Net::HTTP" do response = Net::HTTP.start("www.greetings.com") do |http| http.request(Net::HTTP::Get.new("/")) end expect(response.body).to eq("Hello, world") end it "can be accessed using Net::HTTP#get_response" do response = Net::HTTP.get_response(URI.parse("http://www.greetings.com/")) expect(response.body).to eq("Hello, world") end it "can be accessed using open-uri" do response = open("http://www.greetings.com") expect(response.status).to eq(["200", "OK"]) expect(response.read).to eq("Hello, world") end it "can be accessed using RestClient" do response = RestClient.get("http://www.greetings.com") expect(response.code).to eq(200) expect(response.to_s).to eq("Hello, world") end it "can be accessed using Mechanize" do response = Mechanize.new.get("http://www.greetings.com") expect(response.body).to eq("Hello, world") end it "can be accessed using Patron" do patron = Patron::Session.new response = patron.get("http://www.greetings.com/foo/bar") expect(response.body).to eq("Hello, world") end end describe ".at" do context "with a block" do it "mounts associated block as an app" do ShamRack.at("simple.xyz") do |env| ["200 OK", { "Content-type" => "text/plain" }, ["Easy, huh?"]] end expect(open("http://simple.xyz").read).to eq("Easy, huh?") end end context "with a URL" do it "raises an ArgumentError" do expect do ShamRack.at("http://www.greetings.com") end.to raise_error(ArgumentError, "invalid address") end end describe "#mount" do it "mounts an app" do ShamRack.at("hello.xyz").mount(GreetingApp.new) expect(open("http://hello.xyz").read).to eq("Hello, world") end end describe "#unmount" do it "deregisters a mounted app" do ShamRack.at("gone.xyz").mount(GreetingApp.new) ShamRack.at("gone.xyz").unmount expect do open("http://gone.xyz").read end.to raise_error(NetHttpProhibited) end end describe "#rackup" do before do @return_value = ShamRack.at("rackup.xyz").rackup do use UpcaseBody run GreetingApp.new end end it "mounts an app created using Rack::Builder" do expect(open("http://rackup.xyz").read).to eq("HELLO, WORLD") end it "returns the app" do expect(@return_value).to respond_to(:call) end end describe "#sinatra" do before do @return_value = ShamRack.at("sinatra.xyz").sinatra do get "/hello/:subject" do "Hello, #{params[:subject]}" end end end it "mounts associated block as a Sinatra app" do expect(open("http://sinatra.xyz/hello/stranger").read).to eq("Hello, stranger") end it "returns the app" do expect(@return_value).to respond_to(:call) end end describe "#stub" do before do @return_value = ShamRack.at("stubbed.xyz").stub end it "mounts a StubWebService" do expect(ShamRack.application_for("stubbed.xyz")).to be_kind_of(ShamRack::StubWebService) end it "returns the StubWebService" do expect(@return_value).to eq(ShamRack.application_for("stubbed.xyz")) end end end describe "response" do before(:each) do ShamRack.at("www.greetings.com") do [ "456 Foo Bar", { "Content-Type" => "text/plain", "X-Foo" => "bar" }, ["BODY"] ] end end let(:response) { Net::HTTP.get_response(URI.parse("http://www.greetings.com/")) } it "has status returned by app" do expect(response.code).to eq("456") end it "has status message returned by app" do expect(response.message).to eq("Foo Bar") end it "has body returned by app" do expect(response.body).to eq("BODY") end it "has Content-Type returned by app" do expect(response.content_type).to eq("text/plain") end it "has other headers returned by app" do expect(response["x-foo"]).to eq("bar") end context "when the app returns a numeric status" do before(:each) do ShamRack.at("www.greetings.com") do [ 201, { "Content-Type" => "text/plain" }, ["BODY"] ] end @response = Net::HTTP.get_response(URI.parse("http://www.greetings.com/")) end it "has status returned by app" do expect(response.code).to eq("201") end it "derives a status message" do expect(response.message).to eq("Created") end end end describe ".allow_network_connections" do context "when false" do before do ShamRack.prevent_network_connections end after do ShamRack.allow_network_connections end it "prevents Net::HTTP requests" do expect { Net::HTTP.get_response(URI.parse("http://www.example.com/")) }.to raise_error(ShamRack::NetworkConnectionPrevented) end it "prevents Patron requests" do expect { Patron::Session.new.get("http://www.example.com/") }.to raise_error(ShamRack::NetworkConnectionPrevented) end end context "when true" do before do ShamRack.allow_network_connections end it "allows Net::HTTP requests" do expect { Net::HTTP.get_response(URI.parse("http://www.example.com/")) }.to raise_error(NetHttpProhibited) end end end describe "Rack environment" do before(:each) do @env_recorder = recorder = EnvRecorder.new(GreetingApp.new) ShamRack.at("env.xyz").rackup do use Rack::Lint run recorder end end def env @env_recorder.last_env end it "is valid" do open("http://env.xyz/blah?q=abc") expect(env["REQUEST_METHOD"]).to eq("GET") expect(env["SCRIPT_NAME"]).to eq("") expect(env["PATH_INFO"]).to eq("/blah") expect(env["QUERY_STRING"]).to eq("q=abc") expect(env["SERVER_NAME"]).to eq("env.xyz") expect(env["SERVER_PORT"]).to eq("80") expect(env["rack.version"]).to be_kind_of(Array) expect(env["rack.url_scheme"]).to eq("http") expect(env["rack.multithread"]).to eq(true) expect(env["rack.multiprocess"]).to eq(true) expect(env["rack.run_once"]).to eq(false) end it "provides request headers" do Net::HTTP.start("env.xyz") do |http| request = Net::HTTP::Get.new("/") request["Foo-bar"] = "baz" http.request(request) end expect(env["HTTP_FOO_BAR"]).to eq("baz") end it "supports POST" do RestClient.post("http://env.xyz/resource", "q" => "rack") expect(env["REQUEST_METHOD"]).to eq("POST") expect(env["CONTENT_TYPE"]).to eq("application/x-www-form-urlencoded") expect(env["rack.input"].read).to eq("q=rack") end it "supports POST using Net::HTTP" do Net::HTTP.start("env.xyz") do |http| http.post("/resource", "q=rack") end expect(env["REQUEST_METHOD"]).to eq("POST") expect(env["rack.input"].read).to eq("q=rack") end it "supports POST using Patron" do patron = Patron::Session.new response = patron.post("http://env.xyz/resource", "", "Content-Type" => "application/xml") expect(response.status).to eq(200) expect(env["REQUEST_METHOD"]).to eq("POST") expect(env["rack.input"].read).to eq("") expect(env["CONTENT_TYPE"]).to eq("application/xml") end it "supports PUT" do RestClient.put("http://env.xyz/thing1", "stuff", :content_type => "text/plain") expect(env["REQUEST_METHOD"]).to eq("PUT") expect(env["CONTENT_TYPE"]).to eq("text/plain") expect(env["rack.input"].read).to eq("stuff") end it "supports PUT using Patron" do patron = Patron::Session.new response = patron.put("http://env.xyz/resource", "stuff", "Content-Type" => "text/plain") expect(env["REQUEST_METHOD"]).to eq("PUT") expect(env["CONTENT_TYPE"]).to eq("text/plain") expect(env["rack.input"].read).to eq("stuff") end it "supports DELETE" do RestClient.delete("http://env.xyz/thing/1") expect(env["REQUEST_METHOD"]).to eq("DELETE") expect(env["PATH_INFO"]).to eq("/thing/1") end it "supports DELETE using Patron" do patron = Patron::Session.new response = patron.delete("http://env.xyz/resource") expect(env["REQUEST_METHOD"]).to eq("DELETE") expect(env["PATH_INFO"]).to eq("/resource") end end end