Testing the Protocol, Subdomain, and Host of a Rack App with Rack::Test

Israel Palacio

I’ve used Rack::Test to test Rails and other rack-based frameworks for a long time, but the first time I needed to test the protocol (“http” or “https”) of an app I was stumped.

I wanted to write a test that ensured that non-HTTPS requests were rejected in production, but the request methods in Rack::Test take a path (e.g. get "/blog") and the protocol/host always defaults to "http://example.org".

The code being tested looked like this.

class API < Grape::API

  helpers do
    def reject_http!
      error!("The API is only accessible over HTTPS.", 403) if http_request?
    end

    def http_request?
      request.scheme == "http"
    end

    def production?
      ENV["RACK_ENV"] == "production"
    end
  end

  before do
    reject_http! if production?
  end

  get "/" do
    ...
  end

end

After doing some spelunking in the relevant parts of Rack::Test I discovered that although it doesn’t seem to be explicitly documented anywhere and I’d never seen it done before, Rack::Test actually supports making requests to full URLs, which means you can use it to test the protocol, subdomain, and host of a request!

describe API do

  before do
    ENV["RACK_ENV"] = "production"
  end

  after do
    ENV["RACK_ENV"] = "test"
  end

  it "allows HTTPS requests in production" do
    get "https://example.dev"

    last_response.status.must_equal 200
  end

  it "does not allow HTTP requests in production" do
    get "http://example.dev"

    last_response.status.must_equal 403
    error = JSON.parse(last_response.body)["error"]
    error.must_equal "The API is only accessible over HTTPS."
  end

end