From 8b9bdf12f3e32c8b836a096dff61cdff7431bc93 Mon Sep 17 00:00:00 2001 From: Michael Bleigh Date: Fri, 20 Mar 2009 01:47:29 -0400 Subject: [PATCH] Added Dispatcher classes to perform API calls without depending on another Twitter client gem. --- README.markdown | 2 +- app/models/twitter_auth/basic_user.rb | 6 +-- app/models/twitter_auth/generic_user.rb | 8 +++ app/models/twitter_auth/oauth_user.rb | 2 +- lib/twitter_auth.rb | 12 +++++ lib/twitter_auth/dispatcher/basic.rb | 44 +++++++++++++++ lib/twitter_auth/dispatcher/oauth.rb | 23 ++++++++ spec/models/twitter_auth/basic_user_spec.rb | 14 +++++ spec/models/twitter_auth/oauth_user_spec.rb | 15 ++++++ spec/twitter_auth/dispatcher/basic_spec.rb | 59 +++++++++++++++++++++ spec/twitter_auth/dispatcher/oauth_spec.rb | 52 ++++++++++++++++++ spec/twitter_auth_spec.rb | 24 +++++++++ 12 files changed, 254 insertions(+), 7 deletions(-) create mode 100644 lib/twitter_auth/dispatcher/basic.rb create mode 100644 lib/twitter_auth/dispatcher/oauth.rb create mode 100644 spec/twitter_auth/dispatcher/basic_spec.rb create mode 100644 spec/twitter_auth/dispatcher/oauth_spec.rb diff --git a/README.markdown b/README.markdown index 9dca293..50aa12a 100644 --- a/README.markdown +++ b/README.markdown @@ -23,7 +23,7 @@ Usage To utilize TwitterAuth in your application you will need to run the generator: - script/generate twitter_auth [--oauth(default)|--basic] + script/generate twitter_auth [--oauth (default) | --basic] This will generate a migration as well as set up the stubs needed to use the Rails Engines controllers and models set up by TwitterAuth. It will also create a User class that inherits from TwitterUser, abstracting away all of the Twitter authentication functionality and leaving you a blank slate to work with for your application. diff --git a/app/models/twitter_auth/basic_user.rb b/app/models/twitter_auth/basic_user.rb index 829e2b2..f460551 100644 --- a/app/models/twitter_auth/basic_user.rb +++ b/app/models/twitter_auth/basic_user.rb @@ -12,11 +12,7 @@ def self.included(base) module ClassMethods def verify_credentials(login, password) - uri = URI.parse(TwitterAuth.base_url) - net = Net::HTTP.new(uri.host, uri.port) - net.use_ssl = uri.scheme == 'https' - net.read_timeout = TwitterAuth.api_timeout - response = net.start { |http| + response = TwitterAuth.net.start { |http| request = Net::HTTP::Get.new('/account/verify_credentials.json') request.basic_auth login, password http.request(request) diff --git a/app/models/twitter_auth/generic_user.rb b/app/models/twitter_auth/generic_user.rb index 2887a49..0cbc266 100644 --- a/app/models/twitter_auth/generic_user.rb +++ b/app/models/twitter_auth/generic_user.rb @@ -58,5 +58,13 @@ def update_twitter_attributes(hash) else include TwitterAuth::BasicUser end + + def twitter + if TwitterAuth.oauth? + TwitterAuth::Dispatcher::Oauth.new(self) + else + TwitterAuth::Dispatcher::Basic.new(self) + end + end end end diff --git a/app/models/twitter_auth/oauth_user.rb b/app/models/twitter_auth/oauth_user.rb index 815e096..5e0367c 100644 --- a/app/models/twitter_auth/oauth_user.rb +++ b/app/models/twitter_auth/oauth_user.rb @@ -33,6 +33,6 @@ def create_from_twitter_hash_and_token(user_info, access_token) def token OAuth::AccessToken.new(TwitterAuth.consumer, access_token, access_secret) - end + end end end diff --git a/lib/twitter_auth.rb b/lib/twitter_auth.rb index 1b6671e..1415182 100644 --- a/lib/twitter_auth.rb +++ b/lib/twitter_auth.rb @@ -1,4 +1,6 @@ module TwitterAuth + class Error < StandardError; end + def self.config(environment=RAILS_ENV) @config ||= {} @config[environment] ||= YAML.load(File.open(RAILS_ROOT + '/config/twitter_auth.yml').read)[environment] @@ -52,6 +54,16 @@ def self.consumer :site => TwitterAuth.base_url ) end + + def self.net + uri = URI.parse(TwitterAuth.base_url) + net = Net::HTTP.new(uri.host, uri.port) + net.use_ssl = uri.scheme == 'https' + net.read_timeout = TwitterAuth.api_timeout + net + end end require 'twitter_auth/controller_extensions' +require 'twitter_auth/cryptify' +require 'twitter_auth/dispatcher/oauth' diff --git a/lib/twitter_auth/dispatcher/basic.rb b/lib/twitter_auth/dispatcher/basic.rb new file mode 100644 index 0000000..fc5f627 --- /dev/null +++ b/lib/twitter_auth/dispatcher/basic.rb @@ -0,0 +1,44 @@ +require 'net/http' + +module TwitterAuth + module Dispatcher + class Basic + attr_accessor :user + + def initialize(user) + raise TwitterAuth::Error, 'Dispatcher must be initialized with a User.' unless user.is_a?(TwitterAuth::BasicUser) + self.user = user + end + + def request(http_method, path, *arguments) + path << '.json' unless path.match(/\.(:?xml|json)\z/i) + + response = TwitterAuth.net.start{ |http| + req = "Net::HTTP::#{http_method.to_s.capitalize}".constantize.new(path, *arguments) + req.basic_auth user.login, user.password + http.request(req) + } + + JSON.parse(response.body) + rescue JSON::ParserError + response.body + end + + def get(path, *arguments) + request(:get, path, *arguments) + end + + def post(path, *arguments) + request(:post, path, *arguments) + end + + def put(path, *arguments) + request(:put, path, *arguments) + end + + def delete(path, *arguments) + request(:delete, path, *arguments) + end + end + end +end diff --git a/lib/twitter_auth/dispatcher/oauth.rb b/lib/twitter_auth/dispatcher/oauth.rb new file mode 100644 index 0000000..ed8d3fe --- /dev/null +++ b/lib/twitter_auth/dispatcher/oauth.rb @@ -0,0 +1,23 @@ +require 'oauth' + +module TwitterAuth + module Dispatcher + class Oauth < OAuth::AccessToken + attr_accessor :user + + def initialize(user) + raise TwitterAuth::Error, 'Dispatcher must be initialized with a User.' unless user.is_a?(TwitterAuth::OauthUser) + self.user = user + super(TwitterAuth.consumer, user.access_token, user.access_secret) + end + + def request(http_method, path, *arguments) + path << '.json' unless path.match(/\.(:?xml|json)\z/i) + response = super + JSON.parse(response.body) + rescue JSON::ParserError + response.body + end + end + end +end diff --git a/spec/models/twitter_auth/basic_user_spec.rb b/spec/models/twitter_auth/basic_user_spec.rb index 8f6591b..365c78a 100644 --- a/spec/models/twitter_auth/basic_user_spec.rb +++ b/spec/models/twitter_auth/basic_user_spec.rb @@ -105,4 +105,18 @@ user.password.should == 'test' end end + + describe '#twitter' do + before do + @user = Factory.create(:twitter_basic_user) + end + + it 'should be an instance of TwitterAuth::Dispatcher::Basic' do + @user.twitter.class.should == TwitterAuth::Dispatcher::Basic + end + + it 'should have the correct user set' do + @user.twitter.user.should == @user + end + end end diff --git a/spec/models/twitter_auth/oauth_user_spec.rb b/spec/models/twitter_auth/oauth_user_spec.rb index 0dd3a9e..efe4f3c 100644 --- a/spec/models/twitter_auth/oauth_user_spec.rb +++ b/spec/models/twitter_auth/oauth_user_spec.rb @@ -67,4 +67,19 @@ @user.token.secret.should == @user.access_secret end end + + describe '#twitter' do + before do + @user = Factory.create(:twitter_oauth_user, :access_token => 'token', :access_secret => 'secret') + end + + it 'should return a TwitterAuth::Dispatcher::Oauth' do + @user.twitter.should be_a(TwitterAuth::Dispatcher::Oauth) + end + + it 'should use my token and secret' do + @user.twitter.token.should == @user.access_token + @user.twitter.secret.should == @user.access_secret + end + end end diff --git a/spec/twitter_auth/dispatcher/basic_spec.rb b/spec/twitter_auth/dispatcher/basic_spec.rb new file mode 100644 index 0000000..19b5c84 --- /dev/null +++ b/spec/twitter_auth/dispatcher/basic_spec.rb @@ -0,0 +1,59 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe TwitterAuth::Dispatcher::Basic do + before do + stub_basic! + @user = Factory.create(:twitter_basic_user, :login => 'twitterman', :password => 'test') + end + + it 'should require a user as the initialization argument' do + lambda{TwitterAuth::Dispatcher::Basic.new(nil)}.should raise_error(TwitterAuth::Error, 'Dispatcher must be initialized with a User.') + end + + it 'should store the user in an attr_accessor' do + TwitterAuth::Dispatcher::Basic.new(@user).user.should == @user + end + + describe '#request' do + before do + @dispatcher = TwitterAuth::Dispatcher::Basic.new(@user) + FakeWeb.register_uri('https://twitter.com:443/fake.json', :string => {'fake' => true}.to_json) + FakeWeb.register_uri('https://twitter.com:443/fake.xml', :string => 'true') + end + + it 'should automatically parse JSON if valid' do + @dispatcher.request(:get, '/fake.json').should == {'fake' => true} + end + + it 'should return XML as a string' do + @dispatcher.request(:get, '/fake.xml').should == "true" + end + + it 'should append .json to the path if no extension is provided' do + @dispatcher.request(:get, '/fake.json').should == @dispatcher.request(:get, '/fake') + end + + %w(get post put delete).each do |method| + it "should build a #{method} class based on a :#{method} http_method" do + @req = "Net::HTTP::#{method.capitalize}".constantize.new('/fake.json') + "Net::HTTP::#{method.capitalize}".constantize.should_receive(:new).and_return(@req) + @dispatcher.request(method.to_sym, '/fake') + end + end + + it 'should start the HTTP session' do + @net = TwitterAuth.net + TwitterAuth.stub!(:net).and_return(@net) + @net.should_receive(:start) + lambda{@dispatcher.request(:get, '/fake')}.should raise_error(NoMethodError) + end + end + + %w(get post delete put).each do |method| + it "should have a ##{method} method that calls request(:#{method})" do + dispatcher = TwitterAuth::Dispatcher::Basic.new(@user) + dispatcher.should_receive(:request).with(method.to_sym, '/fake.json') + dispatcher.send(method, '/fake.json') + end + end +end diff --git a/spec/twitter_auth/dispatcher/oauth_spec.rb b/spec/twitter_auth/dispatcher/oauth_spec.rb new file mode 100644 index 0000000..f837732 --- /dev/null +++ b/spec/twitter_auth/dispatcher/oauth_spec.rb @@ -0,0 +1,52 @@ +require File.dirname(__FILE__) + '/../../spec_helper' + +describe TwitterAuth::Dispatcher::Oauth do + before do + stub_oauth! + @user = Factory.create(:twitter_oauth_user, :access_token => 'token', :access_secret => 'secret') + end + + it 'should be a child class of OAuth::AccessToken' do + TwitterAuth::Dispatcher::Oauth.new(@user).should be_a(OAuth::AccessToken) + end + + it 'should require initialization of an OauthUser' do + lambda{TwitterAuth::Dispatcher::Oauth.new(nil)}.should raise_error(TwitterAuth::Error, 'Dispatcher must be initialized with a User.') + end + + it 'should store the user in an attr_accessor' do + TwitterAuth::Dispatcher::Oauth.new(@user).user.should == @user + end + + it "should initialize with the user's token and secret" do + d = TwitterAuth::Dispatcher::Oauth.new(@user) + d.token.should == 'token' + d.secret.should == 'secret' + end + + describe '#request' do + before do + @dispatcher = TwitterAuth::Dispatcher::Oauth.new(@user) + FakeWeb.register_uri(:get, 'https://twitter.com:443/fake.json', :string => {'fake' => true}.to_json) + FakeWeb.register_uri(:get, 'https://twitter.com:443/fake.xml', :string => "true") + end + + it 'should automatically parse json' do + result = @dispatcher.request(:get, '/fake.json') + result.should be_a(Hash) + result['fake'].should be_true + end + + it 'should return xml as a string' do + @dispatcher.request(:get, '/fake.xml').should == 'true' + end + + it 'should append .json to the path if no extension is provided' do + @dispatcher.request(:get, '/fake').should == @dispatcher.request(:get, '/fake.json') + end + + it 'should work with verb methods' do + @dispatcher.get('/fake').should == @dispatcher.request(:get, '/fake') + end + end +end diff --git a/spec/twitter_auth_spec.rb b/spec/twitter_auth_spec.rb index 4109994..a333710 100644 --- a/spec/twitter_auth_spec.rb +++ b/spec/twitter_auth_spec.rb @@ -23,6 +23,30 @@ TwitterAuth.stub!(:config).and_return({'api_timeout' => 15}) TwitterAuth.api_timeout.should == 15 end + end + + describe '.net' do + before do + stub_basic! + end + + it 'should return a Net::HTTP object' do + TwitterAuth.net.should be_a(Net::HTTP) + end + + it 'should be SSL if the base_url is' do + TwitterAuth.stub!(:config).and_return({'base_url' => 'http://twitter.com'}) + TwitterAuth.net.use_ssl?.should be_false + TwitterAuth.stub!(:config).and_return({'base_url' => 'https://twitter.com'}) + TwitterAuth.net.use_ssl?.should be_true + end + + it 'should work from the base_url' do + @net = Net::HTTP.new('example.com',80) + Net::HTTP.should_receive(:new).with('example.com',80).and_return(@net) + TwitterAuth.stub!(:config).and_return({'base_url' => 'http://example.com'}) + TwitterAuth.net + end end describe '#config' do