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 +2 -2
- data/config/credentials.yml +6 -0
- data/lib/authentication/client.rb +40 -7
- data/lib/boomloop.rb +2 -0
- data/lib/resources/base.rb +33 -26
- data/test/boomloop_resource_test.rb +65 -21
- metadata +2 -2
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
|
data/config/credentials.yml
CHANGED
@@ -1,17 +1,37 @@
|
|
1
1
|
=begin
|
2
|
-
|
3
|
-
the
|
4
|
-
|
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 =>
|
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
|
-
|
38
|
-
self.
|
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
data/lib/resources/base.rb
CHANGED
@@ -2,21 +2,20 @@ module Boomloop
|
|
2
2
|
module Resources
|
3
3
|
class Base < ::OpenStruct
|
4
4
|
class << self
|
5
|
-
attr_accessor :
|
5
|
+
attr_accessor :client
|
6
6
|
end
|
7
7
|
|
8
8
|
def self.api_base
|
9
|
-
ENV["
|
9
|
+
@@api_base ||= (ENV["BOOMLOOP_API_URL"] ? ENV["BOOMLOOP_API_URL"].gsub(/\/$/, "") : 'http://api.boomloop.com' )
|
10
10
|
end
|
11
11
|
|
12
|
-
def self.
|
13
|
-
@@
|
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
|
49
|
+
url = "#{ api_base }/#{ self.plural }"
|
51
50
|
url += "?#{ params.to_query }" unless params.empty?
|
52
|
-
|
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
|
-
|
60
|
-
attrs =
|
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
|
66
|
-
|
67
|
-
self.new(
|
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.
|
71
|
-
xml =
|
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
|
-
|
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
|
-
|
86
|
+
@result
|
81
87
|
end
|
82
88
|
|
83
89
|
def self.construct_resource_collection(attrs)
|
84
|
-
collection = attrs[self.plural]
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
35
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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-
|
12
|
+
date: 2008-05-26 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|