I’ve been fighting this issue the last couple nights. I wrote earlier about how Rack::PostBodyContentTypeParser can automagically turn a posted JSON object into a Rack / Sinatra params hash. So, I wrote some tests to make sure this was the case and moved on. Well, it turns out in real life things weren’t working and I couldn’t figure out why. Everything looked cool, but the hash wasn’t getting set when I did an AJAX call in the browser – everything was empty. I looked at everything, from the server, to the JS library, to the browser, to setting different content types in prototype.js etc… UGH!
The short of it is that Rack::PostBodyContentTypeParser requires exactly application/json in order to automagically turn the posted JSON object into Rack params and prototype.js (and jquery.js were adding an encoding type of charset=UTF-8 so the entire header entry was coming across as this CONTENT_TYPE: application/json; charset=UTF-8. So, as a fix, I’m just including the Rack::PostBodyContentTypeParser in the Sinatra application with one small change. Here’s the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| module Rack
# A Rack middleware for parsing POST/PUT body data when Content-Type is
# not one of the standard supported types, like <tt>application/json</tt>.
#
# TODO: Find a better name.
#
class PostBodyContentTypeParser
# Constants
#
CONTENT_TYPE = 'CONTENT_TYPE'.freeze
POST_BODY = 'rack.input'.freeze
FORM_INPUT = 'rack.request.form_input'.freeze
FORM_HASH = 'rack.request.form_hash'.freeze
# Supported Content-Types
#
################## turned into regex so it matches type with encoding data...
#APPLICATION_JSON = 'application/json'.freeze
APPLICATION_JSON = /^application\/json/.freeze
def initialize(app)
@app = app
end
def call(env)
case env[CONTENT_TYPE]
when APPLICATION_JSON
env.update(FORM_HASH => JSON.parse(env[POST_BODY].read), FORM_INPUT => env[POST_BODY])
end
@app.call(env)
end
end
end |
I tested that this worked by writing the following:
1
2
3
4
5
6
7
8
9
10
| def test_post_as_json_converts_to_params
# sanity check that post with normal params works...
post '/test_params_as_json', :param1=>"param one"
assert_equal last_response.body,"params[:param1]=param one"
post '/test_params_as_json', {:param1=>"param one"}.to_json, "CONTENT_TYPE"=>"application/json"
assert_equal last_response.body,"params[:param1]=param one"
# this is the problem, adding a charset to the content type seems to breaks rack-contrib/post_body_content_type_parser.rb
post '/test_params_as_json', {:param1=>"param one"}.to_json, "CONTENT_TYPE"=>"application/json; charset=UTF-8"
assert_equal last_response.body,"params[:param1]=param one"
end |
Posted: April 4th, 2010 | Author: jay | Filed under: Code | Tags: charset, json, params, PostBodyContentTypeParser, prototype.js, rack, regex, sinatra, test | 4 Comments »
In attempting to AJAX-ize the site, I had the desire to handle JSON as if it were form post data. Queue a Rack middleware solution. rack-contrib contains a bunch of common middleware extensions, one being the horribly named PostBodyContentTypeParser. To get this working I added:
with all of the rest of the required files.
Added:
1
| use Rack::PostBodyContentTypeParser |
to my application class
And went about over testing it like so:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| def test_json_creates_params_hash
params_hash={"user"=>{"username"=>"testuser","email"=>"test@test.com","password"=>"pass1","password_confirmation"=>"pass1"}}
post '/test_json', params_hash
assert !last_request.params.blank?
assert_equal params_hash, last_request.params
assert last_response.ok?
json_string="{\"user\":{\"password_confirmation\":\"pass1\",\"username\":\"testuser\",\"password\":\"pass1\",\"email\":\"test@test.com\"}}"
post '/test_json', JSON(json_string)
assert !last_request.params.blank?
assert_equal params_hash, last_request.params
assert last_response.ok?
post '/test_json', json_string, "CONTENT_TYPE"=>"application/json"
assert_equal last_request.env["CONTENT_TYPE"], "application/json"
assert !last_request.params.blank?
assert_equal params_hash, last_request.params
assert last_response.ok?
end |
Posted: March 30th, 2010 | Author: jay | Filed under: Code | Tags: json, middleware, params, PostBodyContentTypeParser, rack, ruby, sinatra, testing | No Comments »
Rack::Test uses last_response and last_request objects instead of Rack’s typical request and response objects. This is probably normally fine, but when you are testing functionality that requires accessing the Rack’s normal objects, they aren’t there. I found (in the comments section of this post) that you can fix this by overriding them in your test_helper.rb:
1
2
3
4
5
6
7
8
9
10
11
12
| module Test::Unit
class TestCase
include Rack::Test::Methods
...
def request(*args)
args.empty? ? last_request : rack_test_session.request(*args)
end
...
end
end |
Posted: March 26th, 2010 | Author: jay | Filed under: Code | Tags: last_request, last_response, rack, request, response, ruby, sinatra, test::unit, testing, test_helper.rb | No Comments »
In my case it was an OAuth request token and when it was serialized it must have been larger than the cookie limit. Spent too much time on this.
Posted: March 17th, 2010 | Author: jay | Filed under: Code | Tags: 2k, oauth, rack, session | No Comments »
This may not be the best way to do this, but this is what I used at the top of my test file so that a helper method has a value for Rack’s env['HTTP_HOST']:
1
2
3
4
5
6
7
| class FundastacheUserTest < Test::Unit::TestCase
...
def env
last_request.env['HTTP_HOST']="example.org"
last_request.env
end
... |
The test tests to see that using the activation link (that a user gets in an email) activates the user. It looks something like this:
1
2
3
4
5
6
7
8
9
10
11
12
| def test_using_activation_link_should_activate_account
create_user
user=User.first
path=activation_link(user)
path=~/http\:\/\/example.org(.*)/
get $1
follow_redirect!
assert last_response.ok?
assert last_response.body.include?('Account activated!')
user=User.first
assert user.activated
end |
and the helper method looks something like this:
1
2
3
4
5
6
7
8
9
10
|
module FundAStache
module Helpers
...
def activation_link(user)
"http://#{env['HTTP_HOST']}/user/activate/#{user.activation_token}"
end
...
end
end |
Posted: March 13th, 2010 | Author: jay | Filed under: Code | Tags: env, helper, http_host, rack, ruby, test | No Comments »
Here’s a slightly dumb way to test Rack::Flash in a Sinatra app…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| require File.dirname(__FILE__) + '/../test_helper.rb'
module FundAStache
class Application
get '/rack_flash_test' do
flash[:notice]='rack flash is working'
haml :index
end
end
end
class FundastacheTest < Test::Unit::TestCase
include Rack::Test::Methods
def flash
last_request.env['x-rack.flash']
end
def app
@app ||= FundAStache::Application
end
def test_rack_flash_working
# see fake route above...
get '/rack_flash_test'
assert_not_nil flash
assert last_response.body.include?("rack flash is working")
end
end |
Posted: March 8th, 2010 | Author: admin | Filed under: Code | Tags: Code, flash testing, rack, ruby, sinatra | 1 Comment »