Report abuse


			
#--
# Copyright (c) 2007 Chris Taggart
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require File.dirname(__FILE__) + '/../test_helper'

# Tests FacebookUtilities::Facebook class. NB uses Mocha
class FacebookTest < Test::Unit::TestCase

  def test_should_instantiate_new_facebook_object
    assert FacebookUtilities::Facebook.new
  end

  def test_should_have_session_key_and_user_and_fb_params_as_getters
    assert fb_object.respond_to?(:session_key)
    assert fb_object.respond_to?(:user)
    assert fb_object.respond_to?(:fb_params)
    %w(@session_key @user @fb_params).each do |a|
      assert fb_object.instance_variables.include?(a)      
    end    
  end

  def test_should_have_fb_params_as_getter
    assert fb_object.respond_to?(:fb_params)
    assert fb_object.instance_variables.include?("@fb_params")
  end

  def test_should_instantiate_with_raw_params_and_give_access_to_session_key_and_user
    stub_signature_verication # fake verifying of signature -- we test that separately
    fb = fb_object(dummy_fb_params(:fb_sig_session_key => "1234foo", :fb_sig_user => "987bar"))
    assert_equal "1234foo", fb.session_key
    assert_equal "987bar", fb.user
  end

  def test_should_get_new_session_if_auth_key_in_params_when_instantiating
    stub_signature_verication # fake verifying of signature -- we test that separately
    FacebookUtilities::Facebook.any_instance.expects(:get_new_session).with("foo123")
    fb_object(dummy_fb_params(:auth_token => "foo123"))
  end

  def test_should_not_get_new_session_if_already_have_session_when_instantiating
    stub_signature_verication # fake verifying of signature -- we test that separately
    FacebookUtilities::Facebook.any_instance.expects(:get_new_session).never
    fb_object(dummy_fb_params(:fb_sig_session_key => "1234foo", :auth_token => "foo123"))
  end

  def test_should_return_signature_from_given_params
    assert fb_object.respond_to?(:signature_from)
    assert_equal Digest::MD5.hexdigest(FacebookUtilities::FACEBOOK_API_SECRET), fb_object.signature_from
    assert_equal Digest::MD5.hexdigest("a_param=1234xb_param=5678yc_param=97531t#{FacebookUtilities::FACEBOOK_API_SECRET}"), fb_object.signature_from({:b_param => "5678y", :c_param => "97531t", :a_param => "1234x"})
  end

  def test_should_verify_signature
    assert fb_object.respond_to?(:verify_signature)
    assert !fb_object.verify_signature(dummy_fb_params, "12345")
    assert fb_object.verify_signature({:b_param => "5678y", :c_param => "97531t", :a_param => "1234x"}, Digest::MD5.hexdigest("a_param=1234xb_param=5678yc_param=97531t#{FacebookUtilities::FACEBOOK_API_SECRET}"))
  end

  # 
  # get_fb_params tests
  def test_should_get_fb_params_from_given_params_hash
    stub_signature_verication # fake verifying of signature -- we test that separately
    assert fb_object.respond_to?(:get_fb_params)
    assert_equal ({}), fb_object.get_fb_params
    assert_equal ({:param_1 => "1234a", :time => two_hours_ago}), fb_object.get_fb_params(dummy_fb_params)
    assert_equal ({:param_1 => "1234a", :time => two_hours_ago}), fb_object.get_fb_params(dummy_fb_params(:another_param_b => "def", :bad_fb_sig_param => "xyz"))
  end

  def test_should_return_empty_hash_for_fb_params_if_no_time_param_or_params_are_stale
    stub_signature_verication # fake verifying of signature -- we test that separately
    assert_equal ({:param_1 => "1234a", :time => forty_seven_hours_ago}), fb_object.get_fb_params(dummy_fb_params(:fb_sig_time => forty_seven_hours_ago))
    assert_equal ({}), fb_object.get_fb_params(dummy_fb_params(:fb_sig_time => nil))
    assert_equal ({}), fb_object.get_fb_params(dummy_fb_params(:fb_sig_time => 49.hours.ago.to_f.to_s))
  end

  def test_should_return_empty_hash_for_fb_params_if_signature_doesnt_verify
    stub_signature_verication(false)
    assert_equal ({}), fb_object.get_fb_params(dummy_fb_params)
  end

  def test_should_check_only_fb_params_against_signature
    fb = fb_object
    fb.expects(:verify_signature).with({:param_1 => "1234a", :time =>two_hours_ago},"1234567890").returns(true)
    fb.get_fb_params(dummy_fb_params(:fb_sig => "1234567890"))
  end

  # get_new_session tests
  def test_should_get_new_session_given_auth_token
    assert fb_object.respond_to?(:get_new_session)
    fb = fb_object
    setup_facebook_response
    fb.expects(:post_request).with("facebook.auth.getSession", {:auth_token => "abc123"}).returns(@auth_token_response)
    fb.get_new_session("abc123")
    assert_equal "5f34e11bfb97c762e439e6a5-8055", fb.session_key
    assert_equal "8055", fb.user
  end

  def test_should_fail_o_get_new_session_given_bad_auth_token
    assert fb_object.respond_to?(:get_new_session)
    fb = fb_object
    setup_facebook_response
    fb.expects(:post_request).with("facebook.auth.getSession", {:auth_token => "abc123"}).returns(@error_response)
    assert_raise(FacebookUtilities::Facebook::FacebookApiError) { fb.get_new_session("abc123") }
    assert_nil fb.session_key
    assert_nil fb.user
  end

  # login_to_app_url tests
  def test_should_return_application_login_page_url
    assert fb_object.respond_to?(:login_to_app_url)
    assert_equal "http://www.facebook.com/login.php?v=1.0&api_key=#{FacebookUtilities::FACEBOOK_API_KEY}", fb_object.login_to_app_url
    assert_equal "http://www.facebook.com/login.php?v=1.0&api_key=#{FacebookUtilities::FACEBOOK_API_KEY}&next=http%3A%2F%2Fsome.other.site", fb_object.login_to_app_url(:next => "http://some.other.site")
    long_login_url = fb_object.login_to_app_url(:auth_token => "abc123", :next => "http://some.other.site", :canvas => true, :skipcookie => false)
    %w(http://www.facebook.com/login.php?v=1.0&api_key  &next=http%3A%2F%2Fsome.other.site  &auth_token=abc123 &canvas=true).each do |r| 
      assert_match Regexp.new(Regexp.escape(r)), long_login_url
    end
    assert_no_match /skipcookie/, long_login_url # should not include params set to false
  end

  # app_add_url tests
  def test_should_return_url_to_add_application
    assert fb_object.respond_to?(:add_app_url)
    assert_equal "http://www.facebook.com/add.php?api_key=#{FacebookUtilities::FACEBOOK_API_KEY}", fb_object.add_app_url
    assert_equal "http://www.facebook.com/add.php?api_key=#{FacebookUtilities::FACEBOOK_API_KEY}&next=http%3A%2F%2Fsome.other.site", fb_object.add_app_url(:next => "http://some.other.site")
  end

  # in_canvas? tests
  def test_should_return_true_if_in_facebook_canvas
    stub_signature_verication
    assert !fb_object.in_canvas?
    assert fb_object(dummy_fb_params(:fb_sig_in_canvas => "1")).in_canvas?
  end

  # in_iframe? tests
  def test_should_return_true_if_in_facebook_iframe
    stub_signature_verication
    assert !fb_object.in_iframe?    
    assert fb_object(dummy_fb_params(:fb_sig_in_iframe => "1")).in_iframe?
  end

  # in_frame? tests
  def test_should_return_true_if_in_facebook_frame
    stub_signature_verication
    assert !fb_object.in_frame?
    assert fb_object(dummy_fb_params(:fb_sig_in_canvas => "1")).in_frame?
    assert fb_object(dummy_fb_params(:fb_sig_in_iframe => "1")).in_frame?
  end

  # logged_in_to_app? tests
  def test_should_detect_whether_user_is_logged_in_to_app_and_return_user_id_if_they_are
    assert fb_object.respond_to?(:logged_in_to_app?)
    assert !fb_object(dummy_fb_params).logged_in_to_app?
    fb = fb_object
    fb.instance_variable_set(:@user, "1234")
    assert !fb.logged_in_to_app?
    fb.instance_variable_set(:@session_key, "5678")
    assert_equal "1234", fb.logged_in_to_app? # only logged in if both session_key and user are present (NB think php lib only checks user)
  end

  # added_app? tests
  def test_should_detect_whether_user_has_added_app
    stub_signature_verication
    assert fb_object.respond_to?(:added_app?)
    assert !fb_object.added_app?
    assert !fb_object(dummy_fb_params(:fb_sig_added => "0")).added_app?
    assert fb_object(dummy_fb_params(:fb_sig_added => "1")).added_app?
  end

  # call tests  
  def test_should_call_facebook_with_given_method_and_params_and_return_parsed_xml_response_from_calling_facebook_method
    setup_facebook_response
    fb = fb_object
    assert fb.respond_to?(:call)
    fb.expects(:post_request).with("facebook.users.getLoggedInUser", {:uid => "12345"}).returns(@group_members_response)
    parsed_response = fb.call(:users_get_logged_in_user, {:uid => "12345"})
    assert_kind_of Hash, parsed_response
    assert_kind_of Array, parsed_response["members"]
    assert_equal 4, parsed_response["members"].first["uid"].size
  end

  def test_should_raise_exception_if_facebook_returns_an_error_to_method_call
    setup_facebook_response
    fb = fb_object
    fb.expects(:post_request).with("facebook.users.getLoggedInUser", {:uid => "12345"}).returns(@error_response)
    assert_raise(FacebookUtilities::Facebook::FacebookApiError) { fb.call(:users_get_logged_in_user, {:uid => "12345"}) }
  end

  # post_request tests 
  def test_should_post_request_to_facebook_and_insert_required_parameters
    fb = fb_object
    fb.instance_variable_set(:@session_key, "abc123")
    assert fb.respond_to?(:post_request)
    Time.expects(:now).returns(123.4) # Mock current time
    base_params = { "api_key" => FacebookUtilities::FACEBOOK_API_KEY, 
                        "call_id" => "123.4", 
                        "method" => "facebook.users.getLoggedInUser", 
                        "v" => "1.0",
                        "session_key" => "abc123",
                        "uid" => "12345" }
    expected_params = base_params.merge({"sig" => fb_object.signature_from(base_params)})

    fb.expects(:post_http_request).with("api.facebook.com", "/restserver.php", expected_params).returns("hello world")

    assert_equal "hello world", fb.post_request("facebook.users.getLoggedInUser", {:uid => "12345"})    
  end

  def test_should_return_false_in_test_environment_for_post_http_request_without_doing_anything
    Net::HTTP.expects(:new).never
    fb_object.post_request("facebook.users.someMethod", {:some_attribute => "12345"})
  end

  # method_missing tests
  def test_should_call_with_unknown_facebook_method
    fb = fb_object{}
    dummy_params = {:dummy_1 => "foo", :dummy_2 => "bar"}
    fb.expects(:call).with('some_unknown_method', dummy_params).returns("hello world")
    assert_equal "hello world", fb.fb_some_unknown_method(dummy_params)
  end

  def test_should_not_call_with_other_unknown_method
    assert_raise(NoMethodError) { fb_object.some_other_unknown_method({}) }
  end

  private
  def dummy_fb_params(options={})
    {:fb_sig_param_1 => "1234a", :another_param_a => "abc", :fb_sig_time => two_hours_ago}.merge(options)
  end

  def fb_object(params={})
    FacebookUtilities::Facebook.new(params)
  end

  def stub_signature_verication(result=true)
    FacebookUtilities::Facebook.any_instance.stubs(:verify_signature).returns(result)
  end

  def two_hours_ago
    @two_hours_ago ||=2.hours.ago.to_f.to_s
  end

  def forty_seven_hours_ago
    @forty_seven_hours_ago ||=47.hours.ago.to_f.to_s
  end

  def setup_facebook_response
    @user_info_response = <<-EOF
      
      1234567
    EOF

    @group_members_response = <<-EOF
      
      
        
          4567
          5678
          6789
          7890
        
        
          1234567
        
        
        
      
    EOF

    @auth_token_response = <<-EOF
      
      
          5f34e11bfb97c762e439e6a5-8055
          8055
          1173309298
      
    EOF

    @error_response = <<-EOF
      
      
        5
        Unauthorized source IP address (ip was: 10.1.2.3)
        
          
            method
            facebook.friends.get
          
          
            session_key
            373443c857fcda2e410e349c-i7nF4PqX4IW4.
          
          
            api_key
            0289b21f46b2ee642d5c42145df5489f
          
          
            call_id
            1170813376.3544
          
          
            v
            1.0
          
          
            sig
            570dcc2b764578af350ea1e1622349a0
          
        
      
    EOF

  end

end

# This is a dummy controller which includes FacebookUtilities::ControllerUtilities. NB we use exclusively posts to test this as that's what Facebook does
class DummyFacebookController < ApplicationController
  include FacebookUtilities::ControllerUtilities
  before_filter :require_fb_frame, :only => [:guest]
  before_filter :require_logged_in_to_fb_app, :only => [:show]
  before_filter :require_added_fb_app, :only => [:member]

  def guest
    render :text => "called from facebook"
  end

  def show
    render :text => "User is logged-in to app"
  end

  def member
    render :text => "User has added app"
  end

  def redirect_action
    fb_redirect_to params[:url]
  end

  # used in dummy controller test to get access to private methods
  def priv_method(meth_name, args=nil)
    args ? self.send(meth_name, args) : self.send(meth_name)
  end

  # Re-raise errors caught by the controller.
  def rescue_action(e) 
    raise e 
  end
end

class DummyFacebookControllerTest < Test::Unit::TestCase

  def setup
    @controller = DummyFacebookController.new
    @request    = ActionController::TestRequest.new
    @response   = ActionController::TestResponse.new
  end

  # require_fb_frame tests
  def test_should_require_request_to_be_made_from_fb_frame
    assert !@controller.respond_to?(:require_fb_frame)
    assert @controller.respond_to?(:require_fb_frame, true)
    post :guest
    assert_redirected_to "http://www.facebook.com/login.php?v=1.0&api_key=#{FacebookUtilities::FACEBOOK_API_KEY}"
    FacebookUtilities::Facebook.any_instance.expects(:in_frame?).returns(true)
    post :guest
    assert_equal "called from facebook", @response.body #redirects_to Facebook login page
  end

  # facebook tests
  def test_should_access_facebook_object_through_private_facebook_method_and_store_as_instance_variable
    assert !@controller.respond_to?(:facebook)
    assert @controller.respond_to?(:facebook, true)
    @controller.expects(:params).returns({})
    facebook_object = @controller.priv_method(:facebook)
    assert_kind_of FacebookUtilities::Facebook, facebook_object
    assert_equal facebook_object, @controller.instance_variable_get(:@facebook) 
  end

  def test_should_pass_params_to_facebook_object_when_instantiating
    @controller.expects(:params).returns({:param_a => "foo", :param_b => "bar"})
    FacebookUtilities::Facebook.expects(:new).with({:param_a => "foo", :param_b => "bar"})
    @controller.priv_method(:facebook)
  end

  # fb_redirect_to tests
  def test_should_have_private_method_fb_redirect_to
    assert !@controller.respond_to?(:fb_redirect_to)
    assert @controller.respond_to?(:fb_redirect_to, true)
  end

  def test_should_redirect_to_given_url_when_not_in_fb
    post :redirect_action, {:url => "http://www.dummy.com"}
    assert_redirected_to "http://www.dummy.com"
  end

  def test_should_send_fb_type_redirect_when_in_fb_canvas
    FacebookUtilities::Facebook.any_instance.expects(:in_canvas?).returns(true)
    post :redirect_action, {:url => "http://www.dummy.com"}
    assert_equal '', @response.body
  end

  # This is how php lib does it, handles situation in iframe and external website. Obviously doesn't allow for non-js browsers, but can you use them on FB?
  def test_should_send_javascript_redirect_if_not_in_canvas_and_redirecting_to_fb_url
    post :redirect_action, {:url => "http://some.facebook.com/address"}
    assert_equal "", @response.body
  end

  # require_logged_in_to_fb_app tests  
  def test_should_require_user_to_have_logged_in_to_fb_app_and_redirect_if_not
    assert !@controller.respond_to?(:require_logged_in_to_fb_app)
    assert @controller.respond_to?(:require_logged_in_to_fb_app, true) # require_logged_in_to_fb_app is a private method
    FacebookUtilities::Facebook.any_instance.expects(:logged_in_to_app?).returns(true)
    post :show
    assert_equal "User is logged-in to app", @response.body
    FacebookUtilities::Facebook.any_instance.expects(:logged_in_to_app?).returns(false)
    FacebookUtilities::Facebook.any_instance.expects(:login_to_app_url).returns("http://some.other.url")
    post :show
    assert_redirected_to "http://some.other.url" # redirects using redirect_to if external url
  end

  def test_should_redirect_to_facebook_login_page_with_canvas_param_set_only_if_in_facebook_frame
    FacebookUtilities::Facebook.any_instance.expects(:in_frame?).returns(false)
    post :show
    assert_equal "", @response.body
    FacebookUtilities::Facebook.any_instance.expects(:in_frame?).returns(true)
    post :show
    assert_equal "", @response.body
  end

  # require_added_fb_app tests
  def test_should_require_user_to_have_added_fb_app_and_redirect_if_not
    assert !@controller.respond_to?(:require_added_fb_app)
    assert @controller.respond_to?(:require_added_fb_app, true) # require_added_fb_app is a private method
    FacebookUtilities::Facebook.any_instance.expects(:added_app?).returns(true)
    post :member
    assert_equal "User has added app", @response.body
    FacebookUtilities::Facebook.any_instance.expects(:added_app?).returns(false)
    post :member
    assert_equal "", @response.body
  end

end