Wednesday, December 5, 2012

Ruby on Rails - RSpec Tutorial

Hello,

It seems that there isn't a lot of RSpec tutorial that's clear out there.
Let's see if I can write one that's clear.

Assuming that you already have a Ruby on Rails app and are trying to add unit test to your app using RSpec:

  1. Add "gem 'rspec'" to your Gemfile
  2. Run ".../project$ bundle install"
  3. After the RSpec gem is installed, run ".../project$ rails generate rspec:install"
You will see that a "spec" folder is created inside your project folder:
project/
  app/
  config/
  Gemfile
  ...
  spec/

Inside the "spec" folder, you have a "spec_helper.rb" file.
That helper file makes sure that all necessary files under test are loaded.
It contains the following:

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'

# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

RSpec.configure do |config|
  # ## Mock Framework
  #
  # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
  #
  # config.mock_with :mocha
  # config.mock_with :flexmock
  # config.mock_with :rr

  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # If true, the base class of anonymous controllers will be inferred
  # automatically. This will be the default behavior in future versions of
  # rspec-rails.
  config.infer_base_class_for_anonymous_controllers = false

  # Run specs in random order to surface order dependencies. If you find an
  # order dependency and want to debug it, you can fix the order by providing
  # the seed, which is printed after each run.
  #     --seed 1234
  config.order = "random"
end

As you can see, the environment is set to "test." You can change it if you would like. 
Also, bolded lines in blue above are not commented out by default. If you do not use a relational DB (like me, I use MongoDB), feel free to comment them out.

Create the "controllers," "models," and "views" folders inside the "spec" folder:
spec/
  controllers/
  models/
  views/

Let's say you would like to create a test file for one of your controllers, posts_controller.rb, go into the "spec/controllers" folder, create a file called "posts_controller_spec.rb." Make sure that "_spec" is on the file name because that's how RSpec identifies its test files.
Assuming that you have an action called show within your posts_controller.rb, open up the "post_controller_spec.rb" and add the following:

#The spec_helper makes sure that we load all necessary files in.
require 'spec_helper'

#Test the PostsController
describe PostsController do
  #Test the show action
  describe 'show' do
    it "returns all posts" do
      get :show 
      response.should_not be_nil
    end
  end
end
      
Save the file.
Go out to your project directory and run ".../project$ rake spec"
The "rake spec" command is to run all unit tests within your "spec" folder.
To run the unit test in an individual file, execute ".../project$ rspec spec/controllers/posts_controller_spec.rb"

That's all there is to it. Hope I was being clear.
Please feel free to leave a comment if I am not.

LinkedIn REST URL (Step 3 - Obtain Access Token)

Hello again,

My last post was Step 2 for obtaining authorization http://dailyprogrammingtalk.blogspot.com/2012/11/linkedin-rest-url-step-2-obtain.html

We will go through the final step of OAuth, that is obtaining an access token, so we can make API calls.
Using the new OAuth token and verfier we obtained from Step 2, make a POST request to:

https://api.linkedin.com/uas/oauth/requestToken

You might notice that the path of the URL is the same as the one we used in Step 1. There's no typo. Don't worry :)

To make the POST request, you can still use the same code in Step 1, but you need to pass in the OAuth token, verifier, and token secret from Step 2:

OAuthBase oauth = new OAuthBase();
string linkedinUrl = "https://api.linkedin.com/uas/oauth/requestToken"
string url = String.Empty;
string urlParameters = String.Empty;
string timeStamp = oauth.GenerateTimeStamp();
string nonce = oauth.GenerateNonce();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(linkedinUrl);
request.Method = "POST";
string signature = oauth.GenerateSignature(new Uri(linkedinUrl), consumerKey, consumerSecret, step2Tokenstep2TokenSecret, "POST", timeStamp, nonce, out url, out urlParameters);

But then, for the OAuth library I am using, the GenerateSignature method does not accept verifier in the arguments. In case you are curious which OAuth library I use, it's from http://oauth.googlecode.com/svn/code/csharp/OAuthBase.cs
So, I need to make modifications to the GenerateSignatureBase method to accept verifier in the arguments and add the verifier to the list of QueryParameter like below:

public string GenerateSignatureBase(Uri url, string consumerKey, string token, string tokenSecret, string httpMethod, string timeStamp, string verifierstring nonce, string signatureType, out string normalizedUrl, out string normalizedRequestParameters)
{
  ...
  List<QueryParameter> parameters = GetQueryParameters(url.Query);
  ...

  if (!String.IsNullOrEmpty(verifier))
  {
    parameters.Add(new QueryParameter("oauth_verifier", verifier));
  }
  ...
}

After making the changes above, add the extra "verifier" argument to the GenerateSignature methods that make a call to the GenerateSignatureBase method.
After that, you can pass in the verifier value:

string signature = oauth.GenerateSignature(new Uri(linkedinUrl), consumerKey, consumerSecret, step2Tokenstep2TokenSecret, "POST", timeStamp, step2Verifier, nonce, out url, out urlParameters);

Next is your authorization header needs to contain the OAuth token and verifier from Step 2:

StringBuilder header = new StringBuilder("OAuth ");
header.AppendFormat("oauth_consumer_key=\"{0}\"", consumerKey);
header.AppendFormat(", oauth_signature_method=\"{0}\"""HMAC-SHA1");
header.AppendFormat(", oauth_signature=\"{0}\"", MsSecurity.Encoder.UrlEncode(signature));
header.AppendFormat(", oauth_nonce=\"{0}\"", nonce);
header.AppendFormat(", oauth_timestamp=\"{0}\"", timeStamp);
header.AppendFormat(", oauth_version=\"{0}\"""1.0");
header.AppendFormat(", oauth_token=\"{0}\"", step2Token);
header.AppendFormat(", oauth_verifier=\"{0}\"", step2Verifier);

Make the POST request and you will receive the access token and token secret in the response:
oauth_token=efgh&oauth_token_secret=def456

From now on, you can forget about everything you stored from Step 1 and 2. The values of oauth_token and oauth_token_secret from Step 3 are the only ones you need to save because you use them in every API call you make.

Parse them out:

string step3Token = Regex.Match(token, @"oauth_token=([^&]+)").Groups[1].Value,
string step3TokenSecret = Regex.Match(token, @"oauth_token_secret=([^&]+)").Groups[1].Value

Now, to test if your oauth_token and oauth_token secret values work, make a GET request to:

http://api.linkedin.com/v1/people

For the signature and authorization header, do the following:

string signature = oauth.GenerateSignature(new Uri(linkedinUrl), consumerKey, consumerSecret, step3Tokenstep3TokenSecret, "POST", timeStamp, nonce, out url, out urlParameters);

StringBuilder header = new StringBuilder("OAuth ");
header.AppendFormat("oauth_consumer_key=\"{0}\"", consumerKey);
header.AppendFormat(", oauth_signature_method=\"{0}\"""HMAC-SHA1");
header.AppendFormat(", oauth_signature=\"{0}\"", MsSecurity.Encoder.UrlEncode(signature));
header.AppendFormat(", oauth_nonce=\"{0}\"", nonce);
header.AppendFormat(", oauth_timestamp=\"{0}\"", timeStamp);
header.AppendFormat(", oauth_version=\"{0}\"""1.0");
header.AppendFormat(", oauth_token=\"{0}\"", step3Token);

Notice that the verifier is no longer needed.
So, after making the GET request, you will see a response similar to:

<?xml version= "1.0" encoding="UTF-8" standalone="yes"?>
<person>
  <first-name>Kirsten</first-name>  
  <last-name>Jones</last-name>
  <headline>Developer Advocate at LinkedIn</headline>
  <site-standard-profile-request>    
    <url>http://www.linkedin.com/profile?viewProfile=&amp;key=3639896&amp;authToken=JdAa&amp;authType=name&amp;trk=api*a119686*s128146*</url>
  </site-standard-profile-request>
</person>

That's all there is to it for OAuth authentication.
Please feel free to leave comments.