#-- # 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