purzelrakete-boomloop 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/bin/boomloop CHANGED
@@ -33,8 +33,8 @@ error(parser) unless options.secret && options.key
33
33
 
34
34
  # negotiate the tokens
35
35
  client = Boomloop::Authentication::Client.new("api", Boomloop::Authentication::Store::YAMLStore.new)
36
- request_token = client.consume(options.key, options.secret) rescue error("Invalid. Check your credentials at #{ Boomloop::Resources::Base.api_base }oauth/applications.")
37
- puts "\nalmost ready! go to #{ request_token.authorize_url }\n\n press enter when you're done... <waiting>\n"
36
+ request_token = client.consume(options.key, options.secret) rescue error("Invalid. Check your credentials at #{ Boomloop::Resources::Base.api_base }/oauth/applications.")
37
+ puts "\nalmost ready! go to #{ request_token.authorize_url.gsub(/\/\/oauth/, '/oauth') }\n\n press enter when you're done... <waiting>\n"
38
38
 
39
39
  while !@access_token && gets
40
40
  @access_token = client.activate rescue nil
@@ -0,0 +1,6 @@
1
+ ---
2
+ api:
3
+ :consumer_key: j2Nl3hhU09Eo4tSstzbJQ
4
+ :access_secret: SweZHltNo4LF4mvG2vqp1nbMRNw7ZZLraP65TvrLic
5
+ :consumer_secret: Uzwy52l8lSVQYZWMVYNv6nphINN0pGrwKmsXiGSUzg
6
+ :access_key: m4qOECcRym7aW4mtGOw9Fw
@@ -1,17 +1,37 @@
1
1
  =begin
2
- The Client takes two arguments - a config key, and an authentication name. When you authorize using
3
- the boomloop script, a config key 'api' is created in the YAMLStore. This holds your the access
4
- tokens so that you don't need to negotiate them every time you want to make a request.
2
+ When using the booomloop script, a config key 'api' is created in the YAMLStore. This holds
3
+ your the access tokens so that you don't need to negotiate them every time you want to make
4
+ a request.
5
+
6
+ The Client can be instantiated like this:
7
+
8
+ require 'boomloop'
9
+ client = Boomloop::Authentication::Client.new("api", Boomloop::Authentication::Store::YAMLStore.new)
10
+
11
+ If you have already activated the 'api' config, you can now get the connection like this:
12
+
13
+ connection = client.create_accesstoken
14
+ connection.get("http://api.boomloop.com/events")
15
+
5
16
  =end
6
17
  module Boomloop
7
18
  module Authentication
8
19
  class Client
9
20
  attr_accessor :store, :user
10
21
  attr_accessor :consumer, :request_token, :access_token
22
+ attr_accessor :connection
23
+
24
+ def connection
25
+ @connection ||= connect
26
+ end
27
+
28
+ def connect
29
+ @connection ||= create_accesstoken
30
+ end
11
31
 
12
32
  def initialize(user, store)
13
33
  self.user = user
14
- self.store = store
34
+ self.store = store
15
35
  end
16
36
 
17
37
  def consume(key, secret)
@@ -29,13 +49,26 @@ module Boomloop
29
49
  end
30
50
 
31
51
  def create_consumer(key, secret)
32
- self.consumer = OAuth::Consumer.new(key, secret, { :site => "http://boomloop.com" })
52
+ self.consumer = OAuth::Consumer.new(key, secret, { :site => Boomloop::Resources::Base.api_base })
33
53
  end
34
54
 
35
55
  def create_accesstoken
36
56
  credentials = self.store.find(self.user)
37
- self.consumer ||= create_consumer(credentials['consumer_key'], credentials['consumer_secret'])
38
- self.access_token ||= OAuth::AccessToken.new(self.consumer, credentials['access_key'], credentials['access_secret'])
57
+ raise_error_if_not_authenticated(credentials)
58
+ self.consumer ||= create_consumer(credentials[:consumer_key], credentials[:consumer_secret])
59
+ self.access_token ||= OAuth::AccessToken.new(self.consumer, credentials[:access_key], credentials[:access_secret])
60
+ end
61
+
62
+ def get(url, options = {})
63
+ connection.get(url, options.merge({ "X-Consumer-Key" => self.consumer.key }))
64
+ end
65
+
66
+ def post(url, params = "")
67
+ connection.post(url, params, { "X-Consumer-Key" => self.consumer.key })
68
+ end
69
+
70
+ def raise_error_if_not_authenticated(credentials)
71
+ raise Boomloop::ApiError.new("You need to authenticate the api before getting an access token. Do this by running the command 'boomloop'") unless credentials[:consumer_secret]
39
72
  end
40
73
  end
41
74
  end
data/lib/boomloop.rb CHANGED
@@ -7,6 +7,8 @@ require 'activesupport'
7
7
 
8
8
  module Boomloop
9
9
  class ApiError < ::StandardError; end
10
+ class AuthorizationError < ApiError; end
11
+ class ValidationError < ApiError; end
10
12
  end
11
13
 
12
14
  Dir.glob(File.dirname(__FILE__) + "/**/*.rb").each do |file|
@@ -2,21 +2,20 @@ module Boomloop
2
2
  module Resources
3
3
  class Base < ::OpenStruct
4
4
  class << self
5
- attr_accessor :connection
5
+ attr_accessor :client
6
6
  end
7
7
 
8
8
  def self.api_base
9
- ENV["BOOMLOOP_API_BASE"] || 'http://api.boomloop.com/'
9
+ @@api_base ||= (ENV["BOOMLOOP_API_URL"] ? ENV["BOOMLOOP_API_URL"].gsub(/\/$/, "") : 'http://api.boomloop.com' )
10
10
  end
11
11
 
12
- def self.connection
13
- @@connection ||= init
12
+ def self.client
13
+ @@client ||= init
14
14
  end
15
15
 
16
16
  # get oauth token
17
17
  def self.init
18
- client = Boomloop::Authentication::Client.new("api", Boomloop::Authentication::Store::YAMLStore.new)
19
- self.connection = client.create_accesstoken
18
+ self.client = Boomloop::Authentication::Client.new("api", Boomloop::Authentication::Store::YAMLStore.new)
20
19
  end
21
20
 
22
21
  def save
@@ -47,44 +46,52 @@ module Boomloop
47
46
 
48
47
  # REST methods
49
48
  def self.index(params = {})
50
- url = api_base + self.plural
49
+ url = "#{ api_base }/#{ self.plural }"
51
50
  url += "?#{ params.to_query }" unless params.empty?
52
- xml = connection.get(url)
53
-
54
- attrs = from_xml(xml)
51
+ res = client.get(url)
52
+ attrs = from_response(res)
55
53
  construct_resource_collection(attrs)
56
54
  end
57
55
 
58
56
  def self.show(url)
59
- xml = connection.get(url)
60
- attrs = from_xml(xml)
57
+ res = client.get(url)
58
+ attrs = from_response(res)
61
59
  self.new(attrs[self.singular])
62
60
  end
63
61
 
64
62
  def self.create(resource)
65
- url = api_base + self.plural
66
- xml = connection.post(url, resource.to_hash.to_query)
67
- self.new(from_xml(xml)[self.singular])
63
+ url = "#{ api_base }/#{ self.plural }"
64
+ res = client.post(url, resource.to_hash.to_query)
65
+ self.new(from_response(res)[self.singular])
68
66
  end
69
67
 
70
- def self.from_xml(response)
71
- xml = nil
72
- begin
73
- xml = Hash.from_xml(response)
74
- rescue Exception => e
75
- raise Boomloop::APIError.new("Response from boomloop could not be parsed. Response was: \n\n #{ response }")
76
- end
68
+ def self.from_response(response)
69
+ xml = response.body
77
70
 
78
- raise Boomloop::APIError.new("There was a problem with your request: \n\n #{ response }") if xml["errors"]
71
+ case response.code.to_i
72
+ when 401
73
+ raise Boomloop::AuthorizationError.new
74
+ when 200...299
75
+ begin
76
+ @result = Hash.from_xml(xml)
77
+ rescue Exception => e
78
+ raise Boomloop::ApiError.new("Response from boomloop could not be parsed. Response was: \n\n #{ response }")
79
+ end
80
+
81
+ raise Boomloop::ValidationError.new("There was a problem with your request: \n\n #{ response }") if @result["errors"]
82
+ else
83
+ raise Boomloop::ApiError.new("boomloop responded with #{ response.class } (#{ response.code }): #{ response.body }")
84
+ end
79
85
 
80
- xml
86
+ @result
81
87
  end
82
88
 
83
89
  def self.construct_resource_collection(attrs)
84
- collection = attrs[self.plural][self.singular]
90
+ collection = attrs[self.plural]
91
+
85
92
  case collection
86
93
  when Array : collection.map { |hash| self.new(hash) }
87
- when Hash : self.new(collection)
94
+ when Hash : self.new(collection[self.singular])
88
95
  end
89
96
  end
90
97
 
@@ -1,8 +1,10 @@
1
1
  require File.dirname(__FILE__) + '/test_helper.rb'
2
2
 
3
3
  context "a boomloop resource" do
4
+
4
5
  setup do
5
- @connection = Boomloop::Resources::Base.connection
6
+ Boomloop::Authentication::Client.class_eval { def raise_error_if_not_authenticated(*args); end }
7
+ @client = Boomloop::Resources::Base.client
6
8
  end
7
9
 
8
10
  specify "should be creatable by passing in a hash of values" do
@@ -16,90 +18,132 @@ context "a boomloop resource" do
16
18
  monster.to_hash.should.equal qualities
17
19
  end
18
20
 
19
- specify "should be showable when calling Boomloop::Resources::Monster.show('#{ Boomloop::Resources::Base.api_base }monsters/fred')" do
20
- @connection.expects(:get).with("#{ Boomloop::Resources::Base.api_base }monsters/fred").at_least_once.returns(%Q[
21
+ specify "should be showable when calling Boomloop::Resources::Monster.show('#{ Boomloop::Resources::Base.api_base }/monsters/fred')" do
22
+ response = mock()
23
+ response.expects(:code).returns 200
24
+ response.expects(:body).returns(<<-XML)
21
25
  <monster>
22
- <resource_url>#{ Boomloop::Resources::Base.api_base }monsters/fred</resource_url>
26
+ <resource_url>#{ Boomloop::Resources::Base.api_base }/monsters/fred</resource_url>
23
27
  <size>huge</size>
24
28
  <affect>sanguine</affect>
25
29
  <name>fred</name>
26
30
  </monster>
27
- ])
31
+ XML
28
32
 
29
- monster = Boomloop::Resources::Monster.show("#{ Boomloop::Resources::Base.api_base }monsters/fred")
33
+ @client.expects(:get).with("#{ Boomloop::Resources::Base.api_base }/monsters/fred").at_least_once.returns(response)
34
+ monster = Boomloop::Resources::Monster.show("#{ Boomloop::Resources::Base.api_base }/monsters/fred")
30
35
  monster.name.should.equal "fred"
31
36
  end
32
37
 
33
38
  specify "should be listable when calling Boomloop::Resources::Monster.index" do
34
- @connection.expects(:get).with("#{ Boomloop::Resources::Base.api_base }monsters").at_least_once.returns(%Q[
35
- <monsters>
39
+ response = mock()
40
+ response.expects(:code).returns 200
41
+ response.expects(:body).returns(<<-XML)
42
+ <monsters type="array">
36
43
  <monster>
37
- <resource_url>#{ Boomloop::Resources::Base.api_base }monsters/fred</resource_url>
44
+ <resource_url>#{ Boomloop::Resources::Base.api_base }/monsters/fred</resource_url>
38
45
  <size>huge</size>
39
46
  <affect>sanguine</affect>
40
47
  <name>fred</name>
41
48
  </monster>
42
49
 
43
50
  <monster>
44
- <resource_url>#{ Boomloop::Resources::Base.api_base }monsters/simon</resource_url>
51
+ <resource_url>#{ Boomloop::Resources::Base.api_base }/monsters/simon</resource_url>
45
52
  <size>middling</size>
46
53
  <affect>indifferent</affect>
47
54
  <name>simon</name>
48
55
  </monster>
49
56
  </monsters>
50
- ])
57
+ XML
51
58
 
59
+ @client.expects(:get).with("#{ Boomloop::Resources::Base.api_base }/monsters").at_least_once.returns(response)
52
60
  monsters = Boomloop::Resources::Monster.index
53
61
  monsters.map { |monster| monster.name }.should.include "fred"
54
62
  end
55
63
 
56
64
  specify "should be listable when calling Boomloop::Resources::Monster.index and there is only one result" do
57
- @connection.expects(:get).with("#{ Boomloop::Resources::Base.api_base }monsters").at_least_once.returns(%Q[
65
+ response = mock()
66
+ response.expects(:code).returns 200
67
+ response.expects(:body).returns(<<-XML)
58
68
  <monsters>
59
69
  <monster>
60
- <resource_url>#{ Boomloop::Resources::Base.api_base }monsters/fred</resource_url>
70
+ <resource_url>#{ Boomloop::Resources::Base.api_base }/monsters/fred</resource_url>
61
71
  <size>huge</size>
62
72
  <affect>sanguine</affect>
63
73
  <name>fred</name>
64
74
  </monster>
65
75
  </monsters>
66
- ])
76
+ XML
67
77
 
78
+ @client.expects(:get).with("#{ Boomloop::Resources::Base.api_base }/monsters").at_least_once.returns(response)
68
79
  monsters = Boomloop::Resources::Monster.index
69
80
  monsters.name.should.equal "fred"
70
81
  end
71
82
 
72
83
  specify "should be listable with attributes when calling Boomloop::Resources::Monster.index(:affect => :sanguine)" do
73
- @connection.expects(:get).with("#{ Boomloop::Resources::Base.api_base }monsters?affect=sanguine").at_least_once.returns(%Q[
84
+ response = mock()
85
+ response.expects(:code).returns 200
86
+ response.expects(:body).returns(<<-XML)
74
87
  <monsters>
75
88
  <monster>
76
- <resource_url>#{ Boomloop::Resources::Base.api_base }monsters/fred</resource_url>
89
+ <resource_url>#{ Boomloop::Resources::Base.api_base }/monsters/fred</resource_url>
77
90
  <size>huge</size>
78
91
  <affect>sanguine</affect>
79
92
  <name>fred</name>
80
93
  </monster>
81
94
  </monsters>
82
- ])
95
+ XML
83
96
 
97
+ @client.expects(:get).with("#{ Boomloop::Resources::Base.api_base }/monsters?affect=sanguine").at_least_once.returns(response)
84
98
  monsters = Boomloop::Resources::Monster.index(:affect => :sanguine)
85
99
  monsters.name.should.equal "fred"
86
100
  end
87
101
 
88
102
  specify "should be savable when calling .save on a Monster instance" do
89
- intended_resource_url = "#{ Boomloop::Resources::Base.api_base }monsters/samuel"
103
+ intended_resource_url = "#{ Boomloop::Resources::Base.api_base }/monsters/samuel"
90
104
  intended_attributes = { :size => :laughable, :affect => :mordant, :name => "samuel" }
91
-
92
- @connection.expects(:post).with("#{ Boomloop::Resources::Base.api_base }monsters", intended_attributes.to_query).at_least_once.returns(%Q[
105
+ response = mock()
106
+ response.expects(:code).returns 201
107
+ response.expects(:body).returns(<<-XML)
93
108
  <monster>
94
109
  <resource_url>#{ intended_resource_url }</resource_url>
95
110
  <size>laughable</size>
96
111
  <affect>mordant</affect>
97
112
  <name>samuel</name>
98
113
  </monster>
99
- ])
114
+ XML
100
115
 
116
+ @client.expects(:post).with("#{ Boomloop::Resources::Base.api_base }/monsters", intended_attributes.to_query).at_least_once.returns(response)
101
117
  monster = Boomloop::Resources::Monster.new(intended_attributes)
102
118
  monster.save
103
119
  monster.resource_url.should.equal intended_resource_url
104
120
  end
121
+
122
+ specify "should raise an Api Error if boomoop returned something other than XML" do
123
+ response = mock()
124
+ response.expects(:code).returns 200
125
+ response.expects(:body).returns(<<-XML)
126
+ XML
127
+
128
+ @client.expects(:get).with("#{ Boomloop::Resources::Base.api_base }/monsters/fred").at_least_once.returns(response)
129
+
130
+ should.raise Boomloop::ApiError do
131
+ Boomloop::Resources::Monster.show("#{ Boomloop::Resources::Base.api_base }/monsters/fred")
132
+ end
133
+ end
134
+
135
+ specify "should raise an error if boomloop returned an Errors XML" do
136
+ response = mock()
137
+ response.expects(:code).returns 200
138
+ response.expects(:body).returns(<<-XML)
139
+ <errors>
140
+ <error>you left the iron on!</error>
141
+ </errors>
142
+ XML
143
+
144
+ @client.expects(:get).with("#{ Boomloop::Resources::Base.api_base }/monsters/fred").at_least_once.returns(response)
145
+ should.raise Boomloop::ValidationError do
146
+ Boomloop::Resources::Monster.show("#{ Boomloop::Resources::Base.api_base }/monsters/fred")
147
+ end
148
+ end
105
149
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: purzelrakete-boomloop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rany Keddo
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-05-21 00:00:00 -07:00
12
+ date: 2008-05-26 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency