podio 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ module Podio
2
+ module User
3
+ include Podio::ResponseWrapper
4
+ extend self
5
+
6
+ def current
7
+ member Podio.connection.get("/user/").body
8
+ end
9
+
10
+ def create(token, attributes)
11
+ response = Podio.connection.post do |req|
12
+ req.url '/user/'
13
+ req.body = attributes.merge(:token => token)
14
+ end
15
+
16
+ response.body['user_id']
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ module Podio
2
+ module UserStatus
3
+ include Podio::ResponseWrapper
4
+ extend self
5
+
6
+ def current
7
+ member Podio.connection.get("/user/status").body
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ module Podio
2
+ module Widget
3
+ include Podio::ResponseWrapper
4
+ extend self
5
+
6
+ def create(ref_type, ref_id, attributes)
7
+ response = Podio.connection.post do |req|
8
+ req.url "/widget/#{ref_type}/#{ref_id}/"
9
+ req.body = attributes
10
+ end
11
+
12
+ response.body['widget_id']
13
+ end
14
+
15
+ def update(id, attributes)
16
+ response = Podio.connection.put do |req|
17
+ req.url "/widget/#{id}"
18
+ req.body = attributes
19
+ end
20
+
21
+ response.status
22
+ end
23
+
24
+ def delete(id)
25
+ Podio.connection.delete("/widget/#{id}").status
26
+ end
27
+
28
+ def update_order(ref_type, ref_id, widget_list)
29
+ response = Podio.connection.put do |req|
30
+ req.url "/widget/#{ref_type}/#{ref_id}/order"
31
+ req.body = widget_list
32
+ end
33
+
34
+ response.status
35
+ end
36
+
37
+ def find(id)
38
+ member Podio.connection.get("/widget/#{id}").body
39
+ end
40
+
41
+ def find_all_for_reference(ref_type, ref_id)
42
+ list Podio.connection.get("/widget/#{ref_type}/#{ref_id}/display/").body
43
+ end
44
+ end
45
+ end
data/lib/podio/client.rb CHANGED
@@ -1,13 +1,7 @@
1
- require 'podio/middleware/logger'
2
- require 'podio/middleware/oauth2'
3
- require 'podio/middleware/mashify'
4
- require 'podio/middleware/podio_api'
5
- require 'podio/middleware/yajl_response'
6
- require 'podio/middleware/error_response'
7
-
8
1
  module Podio
9
2
  class Client
10
- attr_reader :api_url, :api_key, :api_secret, :debug, :oauth_token, :connection
3
+ attr_reader :api_url, :api_key, :api_secret, :debug, :connection
4
+ attr_accessor :oauth_token, :stubs
11
5
 
12
6
  def initialize(options = {})
13
7
  @api_url = options[:api_url] || Podio.api_url || 'https://api.podio.com'
@@ -16,12 +10,23 @@ module Podio
16
10
  @debug = options[:debug] || Podio.debug
17
11
  @oauth_token = options[:oauth_token]
18
12
 
19
- @connection = configure_connection
20
- @oauth_connection = configure_oauth_connection
13
+ if options[:enable_stubs]
14
+ @enable_stubs = true
15
+ @stubs = Faraday::Adapter::Test::Stubs.new
16
+ end
17
+ @test_mode = options[:test_mode]
18
+ @record_mode = options[:record_mode]
19
+
20
+ setup_connections
21
+ end
22
+
23
+ def reset
24
+ setup_connections
21
25
  end
22
26
 
27
+ # Sign in as a user
23
28
  def get_access_token(username, password)
24
- response = connection.post do |req|
29
+ response = @oauth_connection.post do |req|
25
30
  req.url '/oauth/token', :grant_type => 'password', :client_id => api_key, :client_secret => api_secret, :username => username, :password => password
26
31
  end
27
32
 
@@ -29,6 +34,17 @@ module Podio
29
34
  configure_oauth
30
35
  @oauth_token
31
36
  end
37
+
38
+ # Sign in as an app
39
+ def get_access_token_as_app(app_id, app_token)
40
+ response = @oauth_connection.post do |req|
41
+ req.url '/oauth/token', :grant_type => 'app', :client_id => api_key, :client_secret => api_secret, :app_id => app_id, :app_token => app_token
42
+ end
43
+
44
+ @oauth_token = OAuthToken.new(response.body)
45
+ configure_oauth
46
+ @oauth_token
47
+ end
32
48
 
33
49
  def refresh_access_token
34
50
  response = @oauth_connection.post do |req|
@@ -39,37 +55,50 @@ module Podio
39
55
  configure_oauth
40
56
  end
41
57
 
58
+ def configured_headers
59
+ headers = {}
60
+ headers['User-Agent'] = 'Podio Ruby Library'
61
+ headers['authorization'] = "OAuth2 #{oauth_token.access_token}" if oauth_token
62
+ headers['X-Podio-Dry-Run'] = '1' if @test_mode
63
+
64
+ headers
65
+ end
66
+
42
67
  private
43
68
 
44
- def configure_connection
45
- params = {}
46
- params[:oauth_token] = oauth_token.access_token if oauth_token
69
+ def setup_connections
70
+ @connection = configure_connection
71
+ @oauth_connection = configure_oauth_connection
72
+ end
47
73
 
48
- Faraday::Connection.new(:url => api_url, :params => params, :headers => default_headers, :request => {:client => self}) do |builder|
74
+ def configure_connection
75
+ Faraday::Connection.new(:url => api_url, :headers => configured_headers, :request => {:client => self}) do |builder|
49
76
  builder.use Faraday::Request::Yajl
50
77
  builder.use Middleware::PodioApi
51
78
  builder.use Middleware::OAuth2
52
79
  builder.use Middleware::Logger
53
- builder.adapter Faraday.default_adapter
80
+ builder.adapter(*default_adapter)
81
+ builder.use Middleware::ResponseRecorder if @record_mode
54
82
  builder.use Middleware::YajlResponse
55
- builder.use Middleware::Mashify
56
83
  builder.use Middleware::ErrorResponse
84
+ builder.use Middleware::DateConversion
57
85
  end
58
86
  end
59
87
 
88
+ def default_adapter
89
+ @enable_stubs ? [:test, @stubs] : Faraday.default_adapter
90
+ end
91
+
60
92
  def configure_oauth_connection
61
93
  conn = @connection.dup
62
94
  conn.options[:client] = self
63
- conn.params = {}
95
+ conn.headers.delete('authorization')
96
+ conn.headers.delete('X-Podio-Dry-Run') if @test_mode # oauth requests don't really work well in test mode
64
97
  conn
65
98
  end
66
99
 
67
100
  def configure_oauth
68
101
  @connection = configure_connection
69
102
  end
70
-
71
- def default_headers
72
- { :user_agent => 'Podio Ruby Library' }
73
- end
74
103
  end
75
104
  end
@@ -0,0 +1,10 @@
1
+ module Podio
2
+ module Error
3
+ class TokenExpired < StandardError; end
4
+ class AuthorizationError < StandardError; end
5
+ class BadRequestError < StandardError; end
6
+ class ServerError < StandardError; end
7
+ class NotFoundError < StandardError; end
8
+ class GoneError < StandardError; end
9
+ end
10
+ end
@@ -0,0 +1,40 @@
1
+ module Podio
2
+ module Middleware
3
+ class DateConversion < Faraday::Response::Middleware
4
+ def self.register_on_complete(env)
5
+ env[:response].on_complete do |finished_env|
6
+ convert_dates(finished_env[:body])
7
+ end
8
+ end
9
+
10
+ # Converts all attributes ending with "_on" to datetime and ending with "date" to date
11
+ def self.convert_dates(body)
12
+ [body].flatten.compact.each do |hash|
13
+ hash.each do |key, value|
14
+ if value.is_a?(Hash)
15
+ self.convert_dates(value)
16
+ elsif value.is_a?(Array)
17
+ value.each_with_index { |item, index| hash[key][index] = self.convert_field(key, item) }
18
+ else
19
+ hash[key] = self.convert_field(key, value)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def self.convert_field(key, value)
28
+ if key.present?
29
+ if key[-3..-1] == "_on"
30
+ return value.try(:to_s).try(:to_datetime)
31
+ elsif key[-4..-1] == "date"
32
+ return value.try(:to_s).try(:to_date)
33
+ end
34
+ end
35
+ value
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -7,21 +7,21 @@ module Podio
7
7
  env[:response].on_complete do |finished_env|
8
8
  case finished_env[:status]
9
9
  when 400
10
- raise Error::BadRequestError, finished_env[:body].error
10
+ raise Error::BadRequestError, finished_env[:body]
11
11
  when 401
12
- if finished_env[:body]['error'] == 'Unauthorized: expired_token'
13
- raise Error::TokenExpired, finished_env[:body].error
12
+ if finished_env[:body]['error_description'] =~ /expired_token/
13
+ raise Error::TokenExpired, finished_env[:body].inspect
14
14
  else
15
- raise Error::AuthorizationError, finished_env[:body].error
15
+ raise Error::AuthorizationError, finished_env[:body].inspect
16
16
  end
17
17
  when 403
18
- raise Error::AuthorizationError, finished_env[:body].error
18
+ raise Error::AuthorizationError, finished_env[:body].inspect
19
19
  when 404
20
20
  raise Error::NotFoundError, "#{finished_env[:method].to_s.upcase} #{finished_env[:url]}"
21
21
  when 410
22
22
  raise Error::GoneError, "#{finished_env[:method].to_s.upcase} #{finished_env[:url]}"
23
23
  when 500
24
- raise Error::ServerError, finished_env[:body].error
24
+ raise Error::ServerError, finished_env[:body].inspect
25
25
  end
26
26
  end
27
27
  end
@@ -8,7 +8,13 @@ module Podio
8
8
  puts "\n==> #{env[:method].to_s.upcase} #{env[:url]} \n\n"
9
9
  end
10
10
 
11
- @app.call(env)
11
+ begin
12
+ @app.call(env)
13
+ ensure
14
+ if env[:request][:client].debug
15
+ puts "\n== (#{env[:status]}) ==> #{env[:body]}\n\n"
16
+ end
17
+ end
12
18
  end
13
19
 
14
20
  def initialize(app)
@@ -12,8 +12,8 @@ module Podio
12
12
  rescue Error::TokenExpired
13
13
  podio_client.refresh_access_token
14
14
 
15
- params = orig_env[:url].query_values || {}
16
- orig_env[:url].query_values = params.merge('oauth_token' => podio_client.oauth_token.access_token)
15
+ # new access token needs to be put into the header
16
+ orig_env[:request_headers].merge!(podio_client.configured_headers)
17
17
 
18
18
  # redo the request with the new access token
19
19
  @app.call(orig_env)
@@ -0,0 +1,16 @@
1
+ require 'digest/md5'
2
+
3
+ module Podio
4
+ module Middleware
5
+ class ResponseRecorder < Faraday::Response::Middleware
6
+ def self.register_on_complete(env)
7
+ env[:response].on_complete do |finished_env|
8
+ response = "['#{Faraday::Utils.normalize_path(finished_env[:url])}', :#{finished_env[:method]}, #{finished_env[:status]}, #{finished_env[:response_headers]}, '#{finished_env[:body]}']"
9
+
10
+ filename = Digest::MD5.hexdigest(finished_env[:url].request_uri)
11
+ File.open("#{filename}.rack", 'w') { |f| f.write(response) }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module Podio
2
+ module ResponseWrapper
3
+ def member(response)
4
+ response
5
+ end
6
+
7
+ def list(response)
8
+ response
9
+ end
10
+
11
+ def collection(response)
12
+ Struct.new(:all, :count, :total_count).new(response['items'], response['filtered'], response['total'])
13
+ end
14
+ end
15
+ end
data/lib/podio/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Podio
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
data/podio.gemspec CHANGED
@@ -16,10 +16,8 @@ Gem::Specification.new do |s|
16
16
  s.require_paths = ['lib']
17
17
 
18
18
  s.add_runtime_dependency 'faraday', '~> 0.5.1'
19
- s.add_runtime_dependency 'hashie', '~> 0.4.0'
20
- s.add_runtime_dependency 'yajl-ruby', '~> 0.7.0'
21
-
22
- s.add_development_dependency 'fakeweb', '~> 1.3'
19
+ s.add_runtime_dependency 'activesupport', '~> 3.0'
20
+ s.add_runtime_dependency 'yajl-ruby', '~> 0.7'
23
21
 
24
22
  s.description = <<desc
25
23
  The humble beginnings of the Ruby wrapper for the Podio API.
@@ -0,0 +1,40 @@
1
+ require_relative 'test_helper'
2
+
3
+ class AppStoreTest < ActiveSupport::TestCase
4
+ test 'should find single category' do
5
+ category = Podio::Category.find(1)
6
+
7
+ assert_equal 1, category['category_id']
8
+ end
9
+
10
+ test 'should find all categories' do
11
+ categories = Podio::Category.find_all
12
+
13
+ assert_equal Array, categories.functional.class
14
+ assert_not_nil categories.functional.first['category_id']
15
+
16
+ assert_equal Array, categories.vertical.class
17
+ assert_not_nil categories.vertical.first['category_id']
18
+ end
19
+
20
+ test 'should create category' do
21
+ login_as_user(2)
22
+
23
+ status = Podio::Category.create(:type => 'vertical', :name => 'New Category')
24
+ assert_equal 200, status
25
+ end
26
+
27
+ test 'should update category' do
28
+ login_as_user(2)
29
+
30
+ status = Podio::Category.update(1, :name => 'New name')
31
+ assert_equal 204, status
32
+ end
33
+
34
+ test 'should delete category' do
35
+ login_as_user(2)
36
+
37
+ status = Podio::Category.delete(3)
38
+ assert_equal 204, status
39
+ end
40
+ end
data/test/client_test.rb CHANGED
@@ -1,81 +1,74 @@
1
1
  require_relative 'test_helper'
2
2
 
3
- context 'ClientTest' do
4
- context 'client configuration' do
5
- setup do
6
- Podio.configure do |config|
7
- config.api_url = 'https://api.podio.com'
8
- config.api_key = 'client_id'
9
- config.api_secret = 'client_secret'
10
- end
3
+ class ClientTest < ActiveSupport::TestCase
4
+ def setup
5
+ Podio.configure do |config|
6
+ config.api_url = 'https://api.podio.com'
7
+ config.api_key = 'client_id'
8
+ config.api_secret = 'client_secret'
11
9
  end
10
+ end
12
11
 
13
- test 'should configure defaults' do
14
- podio = Podio::Client.new
12
+ test 'should configure defaults' do
13
+ podio = Podio::Client.new
15
14
 
16
- assert_equal 'https://api.podio.com', podio.api_url
17
- assert_equal 'client_id', podio.api_key
18
- assert_equal 'client_secret', podio.api_secret
19
- end
15
+ assert_equal 'https://api.podio.com', podio.api_url
16
+ assert_equal 'client_id', podio.api_key
17
+ assert_equal 'client_secret', podio.api_secret
18
+ end
20
19
 
21
- test 'should overwrite defaults' do
22
- podio = Podio::Client.new(:api_url => 'https://new.podio.com', :api_key => 'new_client_id', :api_secret => 'new_client_secret')
20
+ test 'should overwrite defaults' do
21
+ podio = Podio::Client.new(:api_url => 'https://new.podio.com', :api_key => 'new_client_id', :api_secret => 'new_client_secret')
23
22
 
24
- assert_equal 'https://new.podio.com', podio.api_url
25
- assert_equal 'new_client_id', podio.api_key
26
- assert_equal 'new_client_secret', podio.api_secret
27
- end
23
+ assert_equal 'https://new.podio.com', podio.api_url
24
+ assert_equal 'new_client_id', podio.api_key
25
+ assert_equal 'new_client_secret', podio.api_secret
26
+ end
28
27
 
29
- test 'should setup connection' do
30
- token = Podio::OAuthToken.new('access_token' => 'access', 'refresh_token' => 'refresh')
31
- podio = Podio::Client.new(:oauth_token => token)
28
+ test 'should setup connection' do
29
+ token = Podio::OAuthToken.new('access_token' => 'access', 'refresh_token' => 'refresh')
30
+ podio = Podio::Client.new(:oauth_token => token)
32
31
 
33
- assert_equal token, podio.oauth_token
32
+ assert_equal token, podio.oauth_token
34
33
 
35
- assert_equal({'oauth_token' => 'access'}, podio.connection.params)
36
- end
34
+ assert_equal 'OAuth2 access', podio.connection.headers['authorization']
37
35
  end
38
36
 
39
- context 'oauth2 authorization' do
40
- test 'should get an access token' do
41
- client = Podio::Client.new(:api_url => 'https://api.podio.com', :api_key => 'client_id', :api_secret => 'client_secret')
42
- assert_nil client.oauth_token
43
-
44
- stub_post(
45
- '/oauth/token?grant_type=password&client_id=client_id&client_secret=client_secret&username=username&password=password',
46
- {'access_token' => 'access', 'refresh_token' => 'refresh', 'expires_in' => 3600}
47
- )
37
+ test 'should get an access token' do
38
+ client = Podio.client
39
+ client.oauth_token = nil
40
+ assert_nil client.oauth_token
48
41
 
49
- client.get_access_token('username', 'password')
42
+ client.get_access_token('pollas@hoisthq.com', 'secret')
50
43
 
51
- assert_equal 'access', client.oauth_token.access_token
52
- assert_equal 'refresh', client.oauth_token.refresh_token
53
- end
54
-
55
- test 'should be able to refresh access token' do
56
- client = podio_test_client
44
+ assert_not_nil client.oauth_token.access_token
45
+ assert_not_nil client.oauth_token.refresh_token
46
+ end
57
47
 
58
- stub_post(
59
- '/oauth/token?grant_type=refresh_token&refresh_token=refresh&client_id=client_id&client_secret=client_secret',
60
- {'access_token' => 'new_access', 'refresh_token' => 'refresh', 'expires_in' => 3600}
61
- )
48
+ test 'should be able to refresh access token' do
49
+ client = Podio.client
50
+ old_token = client.oauth_token.dup
62
51
 
63
- client.refresh_access_token
52
+ client.refresh_access_token
64
53
 
65
- assert_equal 'new_access', client.oauth_token.access_token
66
- assert_equal 'refresh', client.oauth_token.refresh_token
67
- end
54
+ assert_not_equal old_token.access_token, client.oauth_token.access_token
55
+ assert_equal old_token.refresh_token, client.oauth_token.refresh_token
68
56
  end
69
57
 
70
- context 'raw connection' do
71
- test 'should be able to make arbitrary requests' do
72
- client = podio_test_client
58
+ test 'should automatically refresh an expired token' do
59
+ # this token is already expired in the test database
60
+ Podio.client.oauth_token = Podio::OAuthToken.new('access_token' => '30da4594eef93528c11df7fb5deb989cd629ea7060a1ce1ced628d19398214c942bcfe0334cf953ef70a80ea1afdfd80183d5c75d19c1f5526ca4c6f6f3471ef', 'refresh_token' => '82e7a11ae187f28a25261599aa6229cd89f8faee48cba18a75d70efae88ba665ced11d43143b7f5bebb31a4103662b851dd2db1879a3947b843259479fccfad3', 'expires_in' => -10)
61
+ Podio.client.reset
73
62
 
74
- stub_get('/?oauth_token=access', { 'messaging' => true, 'version' => '1.0.1' })
75
-
76
- response = client.connection.get('/')
63
+ assert_nothing_raised do
64
+ response = Podio.client.connection.get('/org/')
77
65
  assert_equal 200, response.status
78
- assert_equal({ 'messaging' => true, 'version' => '1.0.1' }, response.body)
79
66
  end
80
67
  end
68
+
69
+ test 'should be able to make arbitrary requests' do
70
+ response = Podio.client.connection.get('/org/')
71
+ assert_equal 200, response.status
72
+ assert response.body.is_a?(Array)
73
+ end
81
74
  end