Rack::Test is a layer on top of Rack's MockRequest similar to Merb's RequestHelper
Clone or download
scepticulous Fix: Fixes digest authentication with Cookies (#235)
Fix: Fixes digest authentication with Cookies

Bug description
-----

In issue #234 a bug regarding digest authentication has been reported.
In order to reproduce it there are two steps necessary:
1. Make sure the app responds with 401 and a digest challange
2. Make sure a Set-Cookie header is set.
If these two conditions are fulfilled, the reported error will
occur:

```
NoMethodError:
  undefined method `host' for "/digest":String
```
The stack trace already indicates that the issue is taking effect in
the cookie jar class.

```
 .../ruby-2.5.1/gems/rack-test-1.0.0/lib/rack/test/cookie_jar.rb:25:in `initialize'
 .../ruby-2.5.1/gems/rack-test-1.0.0/lib/rack/test/cookie_jar.rb:149:in `new'
 .../ruby-2.5.1/gems/rack-test-1.0.0/lib/rack/test/cookie_jar.rb:149:in `block in merge'
 .../ruby-2.5.1/gems/rack-test-1.0.0/lib/rack/test/cookie_jar.rb:148:in `each'
 .../ruby-2.5.1/gems/rack-test-1.0.0/lib/rack/test/cookie_jar.rb:148:in `merge'
 .../ruby-2.5.1/gems/rack-test-1.0.0/lib/rack/mock_session.rb:36:in `request'
 .../ruby-2.5.1/gems/rack-test-1.0.0/lib/rack/test.rb:261:in `process_request'
 .../ruby-2.5.1/gems/rack-test-1.0.0/lib/rack/test.rb:267:in `process_request'
 .../ruby-2.5.1/gems/rack-test-1.0.0/lib/rack/test.rb:119:in `request'
 ./spec/requests/api_spec.rb:16:in `block (3 levels) in <top (required)>'
```

Problem (root cause) description
-----
When a cookie is set and digest authentication is requested, we
invalidly pass `uri.path` to `process_request` instead of just `uri`.

Detailled explanation
----
After some research I came to the following result.
When a digest request is issued the following if-condition is executed:
* https://github.com/rack-test/rack-test/blob/v1.0.0/lib/rack/test.rb#L261
There `process_request` calls itself with `uri.path` instead of `uri`.

Next `@rack_mock_session.request` is called, which in turn calls
[CookieJar#merge](https://github.com/rack-test/rack-test/blob/v1.0.0/lib/rack/mock_session.rb#L34)
with `last_response.headers['Set-Cookie']`.
Merge has a early return if nil is passed as the argument [see code](https://github.com/rack-test/rack-test/blob/v1.0.0/lib/rack/test/cookie_jar.rb#L139).
However if Set-Cookie is provided (a cookie is sent), then the cookie
is split and cookie objects are created per line. When this happends
the uri is passed (which in case of the .path invocation is just a string),
and then the initializer calls uri.host [here](https://github.com/rack-test/rack-test/blob/v1.0.0/lib/rack/test/cookie_jar.rb#L23).
This will then trigger the exception above.

This description also makes clear that this bugs only occurs, when the
user is testing auth-digest and also setting a header during the
authentication. However according to the sample rails app provided,
this might be a normal case for rails apps.
(depending for what they use cookies)
Latest commit 6c07bf5 Sep 24, 2018

README.md

Rack::Test

Gem Version Code Climate Test Coverage

Code: https://github.com/rack-test/rack-test

Description

Rack::Test is a small, simple testing API for Rack apps. It can be used on its own or as a reusable starting point for Web frameworks and testing libraries to build on.

Features

  • Maintains a cookie jar across requests
  • Easily follow redirects when desired
  • Set request headers to be used by all subsequent requests
  • Small footprint. Approximately 200 LOC

Supported platforms

  • 2.2.2+
  • 2.3
  • 2.4
  • JRuby 9.1.+

If you are using Ruby 1.8, 1.9 or JRuby 1.7, use rack-test 0.6.3.

Known incompatibilites

  • rack-test >= 0.71 does not work with older Capybara versions (< 2.17). See #214 for more details.

Examples

(The examples use Test::Unit but it's equally possible to use rack-test with other testing frameworks like rspec.)

require "test/unit"
require "rack/test"

class HomepageTest < Test::Unit::TestCase
  include Rack::Test::Methods

  def app
    app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['All responses are OK']] }
    builder = Rack::Builder.new
    builder.run app
  end

  def test_response_is_ok
    get '/'

    assert last_response.ok?
    assert_equal last_response.body, 'All responses are OK'
  end

  def set_request_headers
    header 'Accept-Charset', 'utf-8'
    get '/'

    assert last_response.ok?
    assert_equal last_response.body, 'All responses are OK'
  end

  def test_response_is_ok_for_other_paths
    get '/other_paths'

    assert last_response.ok?
    assert_equal last_response.body, 'All responses are OK'
  end

  def post_with_json
    # No assertion in this, we just demonstrate how you can post a JSON-encoded string.
    # By default, Rack::Test will use HTTP form encoding if you pass in a Hash as the
    # parameters, so make sure that `json` below is already a JSON-serialized string.
    post(uri, json, { 'CONTENT_TYPE' => 'application/json' })
  end

  def delete_with_url_params_and_body
    delete '/?foo=bar', JSON.generate('baz' => 'zot')
  end
end

If you want to test one app in isolation, you just return that app as shown above. But if you want to test the entire app stack, including middlewares, cascades etc. you need to parse the app defined in config.ru.

OUTER_APP = Rack::Builder.parse_file("config.ru").first

class TestApp < Test::Unit::TestCase
  include Rack::Test::Methods

  def app
    OUTER_APP
  end

  def test_root
    get "/"
    assert last_response.ok?
  end
end

Install

To install the latest release as a gem:

gem install rack-test

Or via Bundler:

gem 'rack-test'

Or to install unreleased version via Bundler:

gem 'rack-test', github: 'rack-test', branch: 'master'

Authors

  • Contributions from Bryan Helmkamp, Simon Rozet, Pat Nakajima and others
  • Much of the original code was extracted from Merb 1.0's request helper

License

rack-test is released under the MIT License.

Contribution

Contributions are welcome. Please make sure to:

  • Use a regular forking workflow
  • Write tests for the new or changed behaviour
  • Provide an explanation/motivation in your commit message / PR message
  • Ensure History.txt is updated

Releasing

  • Ensure History.md is up-to-date
  • Bump VERSION in lib/rack/test/version.rb
  • git commit . -m 'Release 1.1.0'
  • git push
  • bundle exec thor :release
  • Updated the GitHub releases page