purzelrakete-boomloop 0.0.3 → 0.0.4

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