Curl command to test JSON posts
1 | curl -i -H "Content-Type: application/json" -H "Accept: application/json" -X POST -d '{"user":{"username_or_email":"username","password":"password"}}' http://fas/test_json_post |
1 | curl -i -H "Content-Type: application/json" -H "Accept: application/json" -X POST -d '{"user":{"username_or_email":"username","password":"password"}}' http://fas/test_json_post |
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 |
Looking for a reason that autotest is skipping some of my test files….
UPDATE: Found the culprit. In my .autotest file I had some exclusions which were matching on the log in blog
1 2 3 4 5 | Autotest.add_hook :initialize do |autotest| %w{.git .svn .hg .DS_Store db log tmp vendor ._* .sqlite3}.each do |exception| autotest.add_exception(exception) end end |
I think I’ll just comment the exception line out for now as I don’t think it’s necessary.
Posted: March 27th, 2010 | Author: jay | Filed under: Code | Tags: autotest, test, testing | No Comments »Check that you didn’t copy / paste / didn’t change the test class definition line.
example:
My BlogPost class was mis-titled…
1 2 | class FundastacheSiteTest < Test::Unit::TestCase ... |
There went an hour…
Posted: March 25th, 2010 | Author: jay | Filed under: Code | Tags: bug, class, dumb, test | No Comments »I should have done this earlier. To do a number of tests, I have to repeat the same actions like create a user, log a user in, etc. I was putting these actions at the top of each test file, but have wised up and moved them to the test_helper.rb file. Here’s how that looks now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | module Test::Unit class TestCase ... def create_user(user_hash={}) post '/user/signup', {:user=>{:username=>"testuser",:email=>"test@test.com",:password=>"pass1",:password_confirmation=>"pass1"}.merge(user_hash)} end def login(user_hash={}) post "/user/login", :user=>{:username_or_email=>'testuser',:password=>'pass1'}.merge(user_hash) end end end |
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 |
Email is going to be used to activate an account, and I’m leaning towards using the Pony gem to simplify the creations and sending.
Here’s how simple Pony is:
1 | Pony.mail(:to => 'test@example.com', :from=>'test@example.com', :subject=>'spam', :body=>(erb :registration_email)) |
and here’s how you can test it with the pony-test gem:
1 2 3 4 5 6 7 8 9 10 | def test_valid_new_user_signup_should_create_email count=all_email.count create_user assert_equal count+1, all_email.count end def test_signup_email_should_contain_activation_link create_user assert_match /http\:\/\//, current_email.body end |
Don’t forget to nclude the Pony test helpers in your test class…
1 | include Pony::TestHelpers |
How do you test your helper methods in a Sinatra::Base app?
1 2 3 4 5 6 7 | module FundAStache module Helpers def logged_in? false # you shall not pass... end end end |
Include them in your tests…
1 2 3 4 | class FundastacheUserTest < Test::Unit::TestCase include Rack::Test::Methods include FundAStache::Helpers ... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | require 'fundastache' require 'test/unit' require "rack/test" require "rack/flash/test" ENV['RACK_ENV'] = 'test' # set test environment set :environment, :test set :run, false set :raise_errors, true set :logging, false DataMapper.setup(:default, "sqlite3::memory:") DataMapper.auto_migrate! |