podio 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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