purzelrakete-boomloop 0.0.5 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -1
- data/bin/boomloop +2 -2
- data/lib/boomloop.rb +12 -3
- data/lib/{authentication → boomloop/authentication}/client.rb +2 -2
- data/lib/{authentication → boomloop/authentication}/credentials.rb +0 -0
- data/lib/{authentication → boomloop/authentication}/store/base.rb +0 -0
- data/lib/{authentication → boomloop/authentication}/store/yaml_store.rb +0 -0
- data/lib/{resources → boomloop/resources}/base.rb +47 -32
- data/lib/{resources → boomloop/resources}/event.rb +0 -0
- data/lib/{resources → boomloop/resources}/event_series.rb +0 -0
- data/lib/{resources → boomloop/resources}/place.rb +0 -0
- data/lib/{resources → boomloop/resources}/ticket_category.rb +0 -0
- data/lib/{version.rb → boomloop/version.rb} +0 -0
- data/test/boomloop_resource_test.rb +124 -11
- data/test/mocks/monster.rb +2 -0
- data/test/test_helper.rb +2 -1
- metadata +12 -12
data/History.txt
CHANGED
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.
|
37
|
-
puts "\nalmost ready! go to #{ request_token.authorize_url
|
36
|
+
request_token = client.consume(options.key, options.secret) rescue error("Invalid. Check your credentials at #{ Boomloop::Resources::Base.boomloop_base }/oauth/applications.")
|
37
|
+
puts "\nalmost ready! go to #{ request_token.authorize_url }\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/lib/boomloop.rb
CHANGED
@@ -9,8 +9,17 @@ module Boomloop
|
|
9
9
|
class ApiError < ::StandardError; end
|
10
10
|
class AuthorizationError < ApiError; end
|
11
11
|
class ValidationError < ApiError; end
|
12
|
+
class RedirectError < ApiError; end
|
12
13
|
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
$:.unshift File.dirname(__FILE__)
|
16
|
+
|
17
|
+
require 'boomloop/authentication/client'
|
18
|
+
require 'boomloop/authentication/credentials'
|
19
|
+
require 'boomloop/authentication/store/base'
|
20
|
+
require 'boomloop/authentication/store/yaml_store'
|
21
|
+
require 'boomloop/resources/base'
|
22
|
+
require 'boomloop/resources/event'
|
23
|
+
require 'boomloop/resources/event_series'
|
24
|
+
require 'boomloop/resources/place'
|
25
|
+
require 'boomloop/resources/ticket_category'
|
@@ -63,8 +63,8 @@ module Boomloop
|
|
63
63
|
connection.get(url, options.merge({ "X-Consumer-Key" => self.consumer.key }))
|
64
64
|
end
|
65
65
|
|
66
|
-
def post(url, params = "")
|
67
|
-
connection.post(url, params, { "X-Consumer-Key" => self.consumer.key })
|
66
|
+
def post(url, params = "", headers = {})
|
67
|
+
connection.post(url, params, { "X-Consumer-Key" => self.consumer.key, 'Accept'=>'application/xml', 'Content-Type' => 'application/xml' }.merge(headers))
|
68
68
|
end
|
69
69
|
|
70
70
|
def raise_error_if_not_authenticated(credentials)
|
File without changes
|
File without changes
|
File without changes
|
@@ -5,19 +5,6 @@ module Boomloop
|
|
5
5
|
attr_accessor :client
|
6
6
|
end
|
7
7
|
|
8
|
-
def self.api_base
|
9
|
-
@@api_base ||= (ENV["BOOMLOOP_API_URL"] ? ENV["BOOMLOOP_API_URL"].gsub(/\/$/, "") : 'http://api.boomloop.com' )
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.client
|
13
|
-
@@client ||= init
|
14
|
-
end
|
15
|
-
|
16
|
-
# get oauth token
|
17
|
-
def self.init
|
18
|
-
self.client = Boomloop::Authentication::Client.new("api", Boomloop::Authentication::Store::YAMLStore.new)
|
19
|
-
end
|
20
|
-
|
21
8
|
def save
|
22
9
|
returned = self.class.create(self)
|
23
10
|
self.instance_variable_set("@table", returned.to_hash)
|
@@ -28,12 +15,33 @@ module Boomloop
|
|
28
15
|
links = to_hash.keys.select { |key| key =~ /resource-url$/ }
|
29
16
|
end
|
30
17
|
|
18
|
+
def valid?
|
19
|
+
self.errors.blank?
|
20
|
+
end
|
21
|
+
|
31
22
|
def to_hash
|
32
23
|
instance_variable_get("@table")
|
33
24
|
end
|
34
25
|
|
35
|
-
def to_xml
|
36
|
-
self.to_hash.to_xml
|
26
|
+
def to_xml(options = {})
|
27
|
+
self.to_hash.to_xml(options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.api_base
|
31
|
+
@@api_base ||= (ENV["BOOMLOOP_API_URL"] ? ENV["BOOMLOOP_API_URL"].gsub(/\/$/, "") : 'http://api.boomloop.com' )
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.boomloop_base
|
35
|
+
@@boomloop_base ||= self.api_base.gsub /(^http:\/\/)(api.)(.*)/, '\1' + '\3'
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.client
|
39
|
+
@@client ||= init
|
40
|
+
end
|
41
|
+
|
42
|
+
# get oauth token
|
43
|
+
def self.init
|
44
|
+
self.client = Boomloop::Authentication::Client.new("api", Boomloop::Authentication::Store::YAMLStore.new)
|
37
45
|
end
|
38
46
|
|
39
47
|
def self.singular
|
@@ -45,6 +53,12 @@ module Boomloop
|
|
45
53
|
end
|
46
54
|
|
47
55
|
# REST methods
|
56
|
+
def self.show(url)
|
57
|
+
res = client.get(url)
|
58
|
+
attrs = from_response(res)
|
59
|
+
self.new(attrs[self.singular])
|
60
|
+
end
|
61
|
+
|
48
62
|
def self.index(params = {})
|
49
63
|
url = "#{ api_base }/#{ self.plural }"
|
50
64
|
url += "?#{ params.to_query }" unless params.empty?
|
@@ -53,24 +67,33 @@ module Boomloop
|
|
53
67
|
construct_resource_collection(attrs)
|
54
68
|
end
|
55
69
|
|
56
|
-
def self.
|
57
|
-
|
70
|
+
def self.create(resource)
|
71
|
+
resource = resource.kind_of?(self) ? resource : self.new(resource)
|
72
|
+
|
73
|
+
url = "#{ api_base }/#{ self.plural }"
|
74
|
+
res = client.post(url, resource.to_xml(:root => singular.to_sym))
|
58
75
|
attrs = from_response(res)
|
59
76
|
self.new(attrs[self.singular])
|
60
77
|
end
|
61
78
|
|
62
|
-
def self.
|
63
|
-
|
64
|
-
res
|
65
|
-
self.new(from_response(res)[self.singular])
|
79
|
+
def self.follow_redirect(response)
|
80
|
+
res = client.get(response.location)
|
81
|
+
from_response(res, response)
|
66
82
|
end
|
67
83
|
|
68
|
-
def self.from_response(response)
|
84
|
+
def self.from_response(response, old_response=nil)
|
69
85
|
xml = response.body
|
70
86
|
|
71
87
|
case response.code.to_i
|
88
|
+
when 422
|
89
|
+
@result = { self.singular => Hash.from_xml(xml) }
|
72
90
|
when 401
|
73
91
|
raise Boomloop::AuthorizationError.new
|
92
|
+
when 301
|
93
|
+
raise Boomloop::RedirectError.new("Redirect URL seem to be the same as the original. I do not follow!") if
|
94
|
+
old_response and old_response.location and response.location and response.location == old_response.location
|
95
|
+
|
96
|
+
follow_redirect(response)
|
74
97
|
when 200...299
|
75
98
|
begin
|
76
99
|
@result = Hash.from_xml(xml)
|
@@ -81,8 +104,8 @@ module Boomloop
|
|
81
104
|
raise Boomloop::ValidationError.new("There was a problem with your request: \n\n #{ response }") if @result["errors"]
|
82
105
|
else
|
83
106
|
raise Boomloop::ApiError.new("boomloop responded with #{ response.class } (#{ response.code }): #{ response.body }")
|
84
|
-
end
|
85
|
-
|
107
|
+
end
|
108
|
+
|
86
109
|
@result
|
87
110
|
end
|
88
111
|
|
@@ -94,14 +117,6 @@ module Boomloop
|
|
94
117
|
when Hash : self.new(collection[self.singular])
|
95
118
|
end
|
96
119
|
end
|
97
|
-
|
98
|
-
def self.transitive_create(resource)
|
99
|
-
if resource.child_resources
|
100
|
-
child_resources.each { |child| transitive_create(child) }
|
101
|
-
else
|
102
|
-
create(resource)
|
103
|
-
end
|
104
|
-
end
|
105
120
|
end
|
106
121
|
end
|
107
122
|
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
2
|
|
3
|
-
context "
|
3
|
+
context "A boomloop resource" do
|
4
4
|
|
5
5
|
setup do
|
6
6
|
Boomloop::Authentication::Client.class_eval { def raise_error_if_not_authenticated(*args); end }
|
@@ -101,19 +101,32 @@ context "a boomloop resource" do
|
|
101
101
|
|
102
102
|
specify "should be savable when calling .save on a Monster instance" do
|
103
103
|
intended_resource_url = "#{ Boomloop::Resources::Base.api_base }/monsters/samuel"
|
104
|
-
intended_attributes = { :
|
104
|
+
intended_attributes = { :affect => :mordant, :name => "samuel", :size => :laughable }
|
105
|
+
intended_ingoing_xml = <<-XML
|
106
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
107
|
+
<monster>
|
108
|
+
<name>samuel</name>
|
109
|
+
<size type="symbol">laughable</size>
|
110
|
+
<affect type="symbol">mordant</affect>
|
111
|
+
</monster>
|
112
|
+
XML
|
113
|
+
|
114
|
+
intended_return_xml = <<-XML
|
115
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
116
|
+
<monster>
|
117
|
+
<name>samuel</name>
|
118
|
+
<affect type="symbol">mordant</affect>
|
119
|
+
<size type="symbol">laughable</size>
|
120
|
+
<resource-url>#{ Boomloop::Resources::Base.api_base }/monsters/samuel</resource-url>
|
121
|
+
</monster>
|
122
|
+
XML
|
123
|
+
|
105
124
|
response = mock()
|
106
125
|
response.expects(:code).returns 201
|
107
|
-
response.expects(:body).returns(
|
108
|
-
|
109
|
-
|
110
|
-
<size>laughable</size>
|
111
|
-
<affect>mordant</affect>
|
112
|
-
<name>samuel</name>
|
113
|
-
</monster>
|
114
|
-
XML
|
126
|
+
response.expects(:body).returns(intended_return_xml)
|
127
|
+
|
128
|
+
@client.expects(:post).with("#{ Boomloop::Resources::Base.api_base }/monsters", intended_ingoing_xml).at_least_once.returns(response)
|
115
129
|
|
116
|
-
@client.expects(:post).with("#{ Boomloop::Resources::Base.api_base }/monsters", intended_attributes.to_query).at_least_once.returns(response)
|
117
130
|
monster = Boomloop::Resources::Monster.new(intended_attributes)
|
118
131
|
monster.save
|
119
132
|
monster.resource_url.should.equal intended_resource_url
|
@@ -146,4 +159,104 @@ context "a boomloop resource" do
|
|
146
159
|
Boomloop::Resources::Monster.show("#{ Boomloop::Resources::Base.api_base }/monsters/fred")
|
147
160
|
end
|
148
161
|
end
|
162
|
+
|
163
|
+
specify "should follow and return the new resource XML if redirected via 301 on create and resource is a duplicate" do
|
164
|
+
intended_resource_url = "#{ Boomloop::Resources::Base.api_base }/monsters/samuel"
|
165
|
+
intended_attributes = { :size => :huge, :affect => :sanguine, :name => "samuel" }
|
166
|
+
|
167
|
+
response = mock()
|
168
|
+
response.expects(:code).returns 301
|
169
|
+
response.expects(:location).returns intended_resource_url
|
170
|
+
response.expects(:body).returns(<<-XML)
|
171
|
+
<duplicates>
|
172
|
+
<resource_url>#{ intended_resource_url }</resource_url>
|
173
|
+
</duplicates>
|
174
|
+
XML
|
175
|
+
|
176
|
+
intended_response = mock()
|
177
|
+
intended_response.expects(:code).returns 200
|
178
|
+
intended_response.expects(:body).returns(<<-XML)
|
179
|
+
<monster>
|
180
|
+
<resource_url>#{ intended_resource_url }</resource_url>
|
181
|
+
<size>huge</size>
|
182
|
+
<affect>sanguine</affect>
|
183
|
+
<name>samuel</name>
|
184
|
+
</monster>
|
185
|
+
XML
|
186
|
+
|
187
|
+
@client.expects(:post).with("#{ Boomloop::Resources::Base.api_base }/monsters", intended_attributes.to_xml(:root => :monster)).at_least_once.returns(response)
|
188
|
+
@client.expects(:get).with(intended_resource_url).at_least_once.returns(intended_response)
|
189
|
+
|
190
|
+
monster = Boomloop::Resources::Monster.new(intended_attributes)
|
191
|
+
monster.save
|
192
|
+
monster.resource_url.should.equal intended_resource_url
|
193
|
+
end
|
194
|
+
|
195
|
+
specify "should follow and return the new resource XML if redirected via 301 on show" do
|
196
|
+
called_resource_url = "#{ Boomloop::Resources::Base.api_base }/monsters/fred"
|
197
|
+
intended_resource_url = "#{ Boomloop::Resources::Base.api_base }/monsters/samuel"
|
198
|
+
|
199
|
+
response = mock()
|
200
|
+
response.expects(:code).returns 301
|
201
|
+
response.expects(:location).returns intended_resource_url
|
202
|
+
response.expects(:body).returns(<<-XML)
|
203
|
+
<duplicates>
|
204
|
+
<resource_url>#{ intended_resource_url }</resource_url>
|
205
|
+
</duplicates>
|
206
|
+
XML
|
207
|
+
|
208
|
+
second_response = mock()
|
209
|
+
second_response.expects(:code).returns 200
|
210
|
+
second_response.expects(:body).returns(<<-XML)
|
211
|
+
<monster>
|
212
|
+
<resource_url>#{ intended_resource_url }</resource_url>
|
213
|
+
<size>huge</size>
|
214
|
+
<affect>sanguine</affect>
|
215
|
+
<name>samuel</name>
|
216
|
+
</monster>
|
217
|
+
XML
|
218
|
+
|
219
|
+
@client.expects(:get).with(any_of(equals(called_resource_url), equals(intended_resource_url))).at_least_once.returns(response, second_response)
|
220
|
+
monster = Boomloop::Resources::Monster.show(called_resource_url)
|
221
|
+
end
|
222
|
+
|
223
|
+
specify "should raise an exception if response redirects to own resource_url" do
|
224
|
+
intended_resource_url = "#{ Boomloop::Resources::Base.api_base }/monsters/samuel"
|
225
|
+
|
226
|
+
response = mock()
|
227
|
+
response.expects(:code).at_least_once.returns 301
|
228
|
+
response.expects(:location).at_least_once.returns intended_resource_url
|
229
|
+
response.expects(:body).at_least_once.returns(<<-XML)
|
230
|
+
<duplicates>
|
231
|
+
<resource_url>#{ intended_resource_url }</resource_url>
|
232
|
+
</duplicates>
|
233
|
+
XML
|
234
|
+
|
235
|
+
@client.expects(:get).with(intended_resource_url).times(2).returns(response)
|
236
|
+
should.raise Boomloop::RedirectError do
|
237
|
+
Boomloop::Resources::Monster.show(intended_resource_url)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
specify "should have errors if boomloop API returns errors" do
|
242
|
+
monster_attributes = { :title => "Foo" }
|
243
|
+
|
244
|
+
response = mock
|
245
|
+
response.expects(:code).returns 422
|
246
|
+
response.expects(:body).returns(<<-XML)
|
247
|
+
<errors>
|
248
|
+
<error>Location boomloop braucht eine Location.</error>
|
249
|
+
<error>Start date trying to parse nil date, bad stuff.</error>
|
250
|
+
<error>Location boomloop braucht eine Location.</error>
|
251
|
+
<error>Start hour Zeit bitte in HH:MM Format.</error>
|
252
|
+
<error>Beschreibung Hast Du keine Informationen zu diesem Event?</error>
|
253
|
+
<error>trying to parse nil date, bad stuff.</error>
|
254
|
+
</errors>
|
255
|
+
XML
|
256
|
+
|
257
|
+
@client.expects(:post).with("#{ Boomloop::Resources::Base.api_base }/monsters", monster_attributes.to_xml(:root => :monster)).returns(response)
|
258
|
+
|
259
|
+
monster = Boomloop::Resources::Monster.create(monster_attributes)
|
260
|
+
monster.should.not.be.valid
|
261
|
+
end
|
149
262
|
end
|
data/test/mocks/monster.rb
CHANGED
data/test/test_helper.rb
CHANGED
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.9
|
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-
|
12
|
+
date: 2008-06-18 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -31,17 +31,17 @@ extra_rdoc_files:
|
|
31
31
|
- History.txt
|
32
32
|
- README.txt
|
33
33
|
files:
|
34
|
-
- lib/authentication/client.rb
|
35
|
-
- lib/authentication/credentials.rb
|
36
|
-
- lib/authentication/store/base.rb
|
37
|
-
- lib/authentication/store/yaml_store.rb
|
34
|
+
- lib/boomloop/authentication/client.rb
|
35
|
+
- lib/boomloop/authentication/credentials.rb
|
36
|
+
- lib/boomloop/authentication/store/base.rb
|
37
|
+
- lib/boomloop/authentication/store/yaml_store.rb
|
38
38
|
- lib/boomloop.rb
|
39
|
-
- lib/resources/base.rb
|
40
|
-
- lib/resources/event.rb
|
41
|
-
- lib/resources/event_series.rb
|
42
|
-
- lib/resources/place.rb
|
43
|
-
- lib/resources/ticket_category.rb
|
44
|
-
- lib/version.rb
|
39
|
+
- lib/boomloop/resources/base.rb
|
40
|
+
- lib/boomloop/resources/event.rb
|
41
|
+
- lib/boomloop/resources/event_series.rb
|
42
|
+
- lib/boomloop/resources/place.rb
|
43
|
+
- lib/boomloop/resources/ticket_category.rb
|
44
|
+
- lib/boomloop/version.rb
|
45
45
|
- bin/boomloop
|
46
46
|
- History.txt
|
47
47
|
- README.txt
|