elevate 0.5.0 → 0.6.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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/README.md +46 -32
- data/Rakefile +11 -1
- data/elevate.gemspec +1 -3
- data/lib/elevate/dsl.rb +15 -0
- data/lib/elevate/{api.rb → elevate.rb} +12 -0
- data/lib/elevate/http.rb +25 -0
- data/lib/elevate/http/errors.rb +2 -0
- data/lib/elevate/http/http_client.rb +2 -61
- data/lib/elevate/http/request.rb +89 -9
- data/lib/elevate/http/response.rb +94 -4
- data/lib/elevate/http/uri.rb +29 -0
- data/lib/elevate/io_coordinator.rb +76 -11
- data/lib/elevate/operation.rb +134 -1
- data/lib/elevate/task_context.rb +6 -0
- data/lib/elevate/version.rb +1 -1
- data/spec/elevate_spec.rb +159 -0
- data/spec/http/{http_request_spec.rb → request_spec.rb} +13 -13
- data/spec/http_spec.rb +195 -0
- data/spec/io_coordinator_spec.rb +8 -0
- metadata +19 -54
- data/spec/api_spec.rb +0 -72
data/lib/elevate/task_context.rb
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
module Elevate
|
2
|
+
# A blank slate for hosting task blocks.
|
3
|
+
#
|
4
|
+
# Because task blocks run in another thread, it is dangerous to expose them
|
5
|
+
# to the calling context. This class acts as a sandbox for task blocks.
|
6
|
+
#
|
7
|
+
# @api private
|
2
8
|
class TaskContext
|
3
9
|
def initialize(args, &block)
|
4
10
|
metaclass = class << self; self; end
|
data/lib/elevate/version.rb
CHANGED
@@ -0,0 +1,159 @@
|
|
1
|
+
module Bacon
|
2
|
+
class Context
|
3
|
+
include ::Elevate
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
describe Elevate do
|
8
|
+
extend WebStub::SpecHelpers
|
9
|
+
|
10
|
+
describe "#async" do
|
11
|
+
it "runs the specified task asynchronously" do
|
12
|
+
async do
|
13
|
+
task do
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
on_finish do |result, exception|
|
18
|
+
@called = result
|
19
|
+
resume
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
wait_max 1.0 do
|
24
|
+
@called.should.be.true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "passes provided args to the task as instance variables" do
|
29
|
+
async name: "harry" do
|
30
|
+
task do
|
31
|
+
@name
|
32
|
+
end
|
33
|
+
|
34
|
+
on_finish do |name, exception|
|
35
|
+
@result = name
|
36
|
+
resume
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
wait_max 1.0 do
|
41
|
+
@result.should == "harry"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "allows tasks to report progress" do
|
46
|
+
@updates = []
|
47
|
+
|
48
|
+
async do
|
49
|
+
task do
|
50
|
+
sleep 0.1
|
51
|
+
yield 1
|
52
|
+
sleep 0.2
|
53
|
+
yield 2
|
54
|
+
sleep 0.3
|
55
|
+
yield 3
|
56
|
+
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
on_update do |count|
|
61
|
+
@updates << count
|
62
|
+
end
|
63
|
+
|
64
|
+
on_finish do |result, exception|
|
65
|
+
resume
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
wait_max 1.0 do
|
70
|
+
@updates.should == [1,2,3]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "timeouts" do
|
75
|
+
before do
|
76
|
+
stub_request(:get, "http://example.com/").
|
77
|
+
to_return(body: "Hello!", content_type: "text/plain", delay: 1.0)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "does not cancel the operation if it completes in time" do
|
81
|
+
@timed_out = false
|
82
|
+
|
83
|
+
async do
|
84
|
+
timeout 3.0
|
85
|
+
|
86
|
+
task do
|
87
|
+
Elevate::HTTP.get("http://example.com/")
|
88
|
+
|
89
|
+
"finished"
|
90
|
+
end
|
91
|
+
|
92
|
+
on_finish do |result, exception|
|
93
|
+
@result = result
|
94
|
+
resume
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
wait_max 5.0 do
|
99
|
+
@result.should == "finished"
|
100
|
+
@timed_out.should.be.false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "stops the operation when timeout interval has elapsed" do
|
105
|
+
@result = nil
|
106
|
+
|
107
|
+
@task = async do
|
108
|
+
timeout 0.5
|
109
|
+
|
110
|
+
task do
|
111
|
+
Elevate::HTTP.get("http://example.com/")
|
112
|
+
|
113
|
+
"finished"
|
114
|
+
end
|
115
|
+
|
116
|
+
on_finish do |result, exception|
|
117
|
+
@result = result
|
118
|
+
resume
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
wait_max 5.0 do
|
123
|
+
@result.should.not == "finished"
|
124
|
+
|
125
|
+
@task.timed_out?.should.be.true
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it "invokes on_timeout when a timeout occurs" do
|
130
|
+
@result = ""
|
131
|
+
@timed_out = false
|
132
|
+
|
133
|
+
async do
|
134
|
+
timeout 0.5
|
135
|
+
|
136
|
+
task do
|
137
|
+
Elevate::HTTP.get("http://example.com/")
|
138
|
+
|
139
|
+
"finished"
|
140
|
+
end
|
141
|
+
|
142
|
+
on_timeout do
|
143
|
+
@timed_out = true
|
144
|
+
end
|
145
|
+
|
146
|
+
on_finish do |result, exception|
|
147
|
+
@result = result
|
148
|
+
resume
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
wait_max 5.0 do
|
153
|
+
@result.should.not == "finished"
|
154
|
+
@timed_out.should.be.true
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
describe Elevate::HTTP::
|
1
|
+
describe Elevate::HTTP::Request do
|
2
2
|
extend WebStub::SpecHelpers
|
3
3
|
|
4
4
|
before do
|
@@ -11,15 +11,15 @@ describe Elevate::HTTP::HTTPRequest do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it "requires a valid HTTP method" do
|
14
|
-
lambda { Elevate::HTTP::
|
14
|
+
lambda { Elevate::HTTP::Request.new(:invalid, @url) }.should.raise(ArgumentError)
|
15
15
|
end
|
16
16
|
|
17
17
|
it "requires a URL starting with http" do
|
18
|
-
lambda { Elevate::HTTP::
|
18
|
+
lambda { Elevate::HTTP::Request.new(:get, "asdf") }.should.raise(ArgumentError)
|
19
19
|
end
|
20
20
|
|
21
21
|
it "requires the body to be an instance of NSData" do
|
22
|
-
lambda { Elevate::HTTP::
|
22
|
+
lambda { Elevate::HTTP::Request.new(:get, @url, body: @body) }.should.raise(ArgumentError)
|
23
23
|
end
|
24
24
|
|
25
25
|
describe "fulfilling a GET request" do
|
@@ -27,7 +27,7 @@ describe Elevate::HTTP::HTTPRequest do
|
|
27
27
|
stub_request(:get, @url).
|
28
28
|
to_return(body: @body, headers: {"Content-Type" => "text/plain"}, status_code: 201)
|
29
29
|
|
30
|
-
@request = Elevate::HTTP::
|
30
|
+
@request = Elevate::HTTP::Request.new(:get, @url)
|
31
31
|
@response = @request.response
|
32
32
|
end
|
33
33
|
|
@@ -52,7 +52,7 @@ describe Elevate::HTTP::HTTPRequest do
|
|
52
52
|
before do
|
53
53
|
stub_request(:get, @url).with(headers: { "API-Token" => "abc123" }).to_return(body: @body)
|
54
54
|
|
55
|
-
@request = Elevate::HTTP::
|
55
|
+
@request = Elevate::HTTP::Request.new(:get, @url, headers: { "API-Token" => "abc123" })
|
56
56
|
@response = @request.response
|
57
57
|
end
|
58
58
|
|
@@ -67,7 +67,7 @@ describe Elevate::HTTP::HTTPRequest do
|
|
67
67
|
end
|
68
68
|
|
69
69
|
it "sends the body as part of the request" do
|
70
|
-
request = Elevate::HTTP::
|
70
|
+
request = Elevate::HTTP::Request.new(:post, @url, body: @body.dataUsingEncoding(NSUTF8StringEncoding))
|
71
71
|
response = request.response
|
72
72
|
|
73
73
|
NSString.alloc.initWithData(response.body, encoding:NSUTF8StringEncoding).should == @body
|
@@ -82,9 +82,9 @@ describe Elevate::HTTP::HTTPRequest do
|
|
82
82
|
it "aborts the request" do
|
83
83
|
start = Time.now
|
84
84
|
|
85
|
-
request = Elevate::HTTP::
|
86
|
-
request.
|
87
|
-
request.cancel
|
85
|
+
request = Elevate::HTTP::Request.new(:get, @url)
|
86
|
+
request.send
|
87
|
+
request.cancel
|
88
88
|
|
89
89
|
response = request.response # simulate blocking
|
90
90
|
finish = Time.now
|
@@ -93,9 +93,9 @@ describe Elevate::HTTP::HTTPRequest do
|
|
93
93
|
end
|
94
94
|
|
95
95
|
it "sets the response to nil" do
|
96
|
-
request = Elevate::HTTP::
|
97
|
-
request.
|
98
|
-
request.cancel
|
96
|
+
request = Elevate::HTTP::Request.new(:get, @url)
|
97
|
+
request.send
|
98
|
+
request.cancel
|
99
99
|
|
100
100
|
request.response.should.be.nil
|
101
101
|
end
|
data/spec/http_spec.rb
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
describe Elevate::HTTP do
|
2
|
+
extend WebStub::SpecHelpers
|
3
|
+
|
4
|
+
before { disable_network_access! }
|
5
|
+
after { enable_network_access! }
|
6
|
+
|
7
|
+
before do
|
8
|
+
@url = "http://www.example.com/"
|
9
|
+
end
|
10
|
+
|
11
|
+
Elevate::HTTP::Request::METHODS.each do |m|
|
12
|
+
describe ".#{m}" do
|
13
|
+
it "synchronously issues a HTTP #{m} request" do
|
14
|
+
stub = stub_request(m, @url)
|
15
|
+
Elevate::HTTP.send(m, @url)
|
16
|
+
|
17
|
+
stub.should.be.requested
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "Request options" do
|
23
|
+
it "encodes query string of :headers" do
|
24
|
+
stub = stub_request(:get, "#{@url}?a=1&b=hello&c=4.2")
|
25
|
+
|
26
|
+
Elevate::HTTP.get(@url, query: { a: 1, b: "hello", c: "4.2" })
|
27
|
+
|
28
|
+
stub.should.be.requested
|
29
|
+
end
|
30
|
+
|
31
|
+
it "sends headers specified by :headers" do
|
32
|
+
stub = stub_request(:get, @url).
|
33
|
+
with(headers: { "API-Key" => "secret" })
|
34
|
+
|
35
|
+
Elevate::HTTP.get(@url, headers: { "API-Key" => "secret" })
|
36
|
+
|
37
|
+
stub.should.be.requested
|
38
|
+
end
|
39
|
+
|
40
|
+
it "sends body content specified by :body" do
|
41
|
+
stub = stub_request(:post, @url).with(body: "hello")
|
42
|
+
|
43
|
+
Elevate::HTTP.post(@url, body: "hello".dataUsingEncoding(NSUTF8StringEncoding))
|
44
|
+
|
45
|
+
stub.should.be.requested
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "with a JSON body" do
|
49
|
+
it "encodes JSON dictionary specified by :json" do
|
50
|
+
stub = stub_request(:post, @url).with(body: '{"test":"secret"}')
|
51
|
+
|
52
|
+
Elevate::HTTP.post(@url, json: { "test" => "secret" })
|
53
|
+
|
54
|
+
stub.should.be.requested
|
55
|
+
end
|
56
|
+
|
57
|
+
it "encodes JSON array specified by :json" do
|
58
|
+
stub = stub_request(:post, @url).with(body: '["1","2"]')
|
59
|
+
|
60
|
+
Elevate::HTTP.post(@url, json: ["1", "2"])
|
61
|
+
|
62
|
+
stub.should.be.requested
|
63
|
+
end
|
64
|
+
|
65
|
+
it "sets the correct Content-Type" do
|
66
|
+
stub = stub_request(:post, @url).
|
67
|
+
with(body: '{"test":"secret"}', headers: { "Content-Type" => "application/json" })
|
68
|
+
|
69
|
+
Elevate::HTTP.post(@url, json: { "test" => "secret" })
|
70
|
+
|
71
|
+
stub.should.be.requested
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "with a form body" do
|
76
|
+
it "encodes form data specified by :form" do
|
77
|
+
stub = stub_request(:post, @url).with(body: { "test" => "secret", "user" => "matt" })
|
78
|
+
|
79
|
+
Elevate::HTTP.post(@url, form: { "test" => "secret", "user" => "matt" })
|
80
|
+
|
81
|
+
stub.should.be.requested
|
82
|
+
end
|
83
|
+
|
84
|
+
it "sets the correct Content-Type" do
|
85
|
+
stub = stub_request(:post, @url).
|
86
|
+
with(body: { "test" => "secret", "user" => "matt" }, headers: { "Content-Type" => "application/x-www-form-urlencoded" })
|
87
|
+
|
88
|
+
Elevate::HTTP.post(@url, form: { "test" => "secret", "user" => "matt" })
|
89
|
+
|
90
|
+
stub.should.be.requested
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "Response" do
|
96
|
+
it "returns a Response" do
|
97
|
+
stub_request(:get, @url).
|
98
|
+
to_return(body: "hello",
|
99
|
+
headers: { "X-TestHeader" => "Value" },
|
100
|
+
status_code: 204)
|
101
|
+
|
102
|
+
response = Elevate::HTTP.get(@url)
|
103
|
+
|
104
|
+
NSString.alloc.initWithData(response.body, encoding: NSUTF8StringEncoding).should == "hello"
|
105
|
+
NSString.alloc.initWithData(response.raw_body, encoding: NSUTF8StringEncoding).should == "hello"
|
106
|
+
response.error.should.be.nil
|
107
|
+
response.headers.keys.should.include("X-TestHeader")
|
108
|
+
response.status_code.should == 204
|
109
|
+
response.url.should == @url
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "when the response is encoded as JSON" do
|
113
|
+
it "should automatically decode it" do
|
114
|
+
stub_request(:get, @url).
|
115
|
+
to_return(json: { user_id: "3", token: "secret" })
|
116
|
+
|
117
|
+
response = Elevate::HTTP.get(@url)
|
118
|
+
|
119
|
+
response.body.should == { "user_id" => "3", "token" => "secret" }
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "when a JSON dictionary is returned" do
|
123
|
+
it "the returned response should behave like a Hash" do
|
124
|
+
stub_request(:get, @url).
|
125
|
+
to_return(json: { user_id: "3", token: "secret" })
|
126
|
+
|
127
|
+
response = Elevate::HTTP.get(@url)
|
128
|
+
|
129
|
+
response["user_id"].should == "3"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "when a JSON array is returned" do
|
134
|
+
it "the returned response should behave like an Array" do
|
135
|
+
stub_request(:get, @url).
|
136
|
+
to_return(json: ["apple", "orange", "pear"])
|
137
|
+
|
138
|
+
response = Elevate::HTTP.get(@url)
|
139
|
+
|
140
|
+
response.length.should == 3
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "when the response is redirected" do
|
146
|
+
it "redirects to the final URL" do
|
147
|
+
@redirect_url = @url + "redirected"
|
148
|
+
|
149
|
+
stub_request(:get, @redirect_url).to_return(body: "redirected")
|
150
|
+
stub_request(:get, @url).to_redirect(url: @redirect_url)
|
151
|
+
|
152
|
+
response = Elevate::HTTP.get(@url)
|
153
|
+
response.url.should == @redirect_url
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "with an infinite redirect loop" do
|
157
|
+
before do
|
158
|
+
@url2 = @url + "redirect"
|
159
|
+
|
160
|
+
stub_request(:get, @url).to_redirect(url: @url2)
|
161
|
+
stub_request(:get, @url2).to_redirect(url: @url)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "raises a RequestError" do
|
165
|
+
lambda { Elevate::HTTP.get(@url) }.
|
166
|
+
should.raise(Elevate::HTTP::RequestError)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
#describe "when the response has a HTTP error status" do
|
172
|
+
#it "raises RequestError" do
|
173
|
+
#stub_request(m, @url).to_return(status_code: 422)
|
174
|
+
|
175
|
+
#lambda { Elevate::HTTP.send(m, @url) }.should.raise(Elevate::HTTP::RequestError)
|
176
|
+
#end
|
177
|
+
#end
|
178
|
+
|
179
|
+
describe "when the request cannot be fulfilled" do
|
180
|
+
it "raises a RequestError" do
|
181
|
+
lambda { Elevate::HTTP.get(@url) }.
|
182
|
+
should.raise(Elevate::HTTP::RequestError)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe "when the Internet connection is offline" do
|
187
|
+
it "raises an OfflineError" do
|
188
|
+
stub_request(:get, @url).to_fail(code: NSURLErrorNotConnectedToInternet)
|
189
|
+
|
190
|
+
lambda { Elevate::HTTP.get(@url) }.
|
191
|
+
should.raise(Elevate::HTTP::OfflineError)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
data/spec/io_coordinator_spec.rb
CHANGED
@@ -30,6 +30,14 @@ describe Elevate::IOCoordinator do
|
|
30
30
|
lambda { @coordinator.send(method, "hello") }.should.raise(Elevate::CancelledError)
|
31
31
|
end
|
32
32
|
end
|
33
|
+
|
34
|
+
describe "when IO has timed out" do
|
35
|
+
it "raises TimeoutError" do
|
36
|
+
@coordinator.cancel(Elevate::TimeoutError)
|
37
|
+
|
38
|
+
lambda { @coordinator.send(method, "hello") }.should.raise(Elevate::TimeoutError)
|
39
|
+
end
|
40
|
+
end
|
33
41
|
end
|
34
42
|
end
|
35
43
|
|