batch_api 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/changelog.md +17 -0
- data/lib/batch_api.rb +6 -1
- data/lib/batch_api/batch_error.rb +41 -0
- data/lib/batch_api/configuration.rb +31 -21
- data/lib/batch_api/error_wrapper.rb +44 -0
- data/lib/batch_api/internal_middleware.rb +87 -0
- data/lib/batch_api/internal_middleware/decode_json_body.rb +24 -0
- data/lib/batch_api/internal_middleware/response_filter.rb +27 -0
- data/lib/batch_api/operation/rack.rb +4 -5
- data/lib/batch_api/processor.rb +22 -20
- data/lib/batch_api/processor/executor.rb +18 -0
- data/lib/batch_api/processor/sequential.rb +29 -0
- data/lib/batch_api/{middleware.rb → rack_middleware.rb} +2 -2
- data/lib/batch_api/response.rb +10 -8
- data/lib/batch_api/version.rb +1 -1
- data/readme.md +179 -106
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +15 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/javascripts/endpoints.js +2 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/assets/stylesheets/endpoints.css +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/endpoints_controller.rb +36 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/helpers/endpoints_helper.rb +2 -0
- data/spec/dummy/app/views/endpoints/get.html.erb +2 -0
- data/spec/dummy/app/views/endpoints/post.html.erb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +63 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +64 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +1742 -0
- data/spec/dummy/log/test.log +48237 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/test/functional/endpoints_controller_test.rb +14 -0
- data/spec/dummy/test/unit/helpers/endpoints_helper_test.rb +4 -0
- data/spec/integration/rails_spec.rb +10 -0
- data/spec/integration/shared_examples.rb +256 -0
- data/spec/integration/sinatra_integration_spec.rb +14 -0
- data/spec/lib/batch_api_spec.rb +20 -0
- data/spec/lib/batch_error_spec.rb +23 -0
- data/spec/lib/configuration_spec.rb +30 -0
- data/spec/lib/error_wrapper_spec.rb +68 -0
- data/spec/lib/internal_middleware/decode_json_body_spec.rb +37 -0
- data/spec/lib/internal_middleware/response_filter_spec.rb +61 -0
- data/spec/lib/internal_middleware_spec.rb +91 -0
- data/spec/lib/operation/rack_spec.rb +243 -0
- data/spec/lib/operation/rails_spec.rb +100 -0
- data/spec/lib/processor/executor_spec.rb +22 -0
- data/spec/lib/processor/sequential_spec.rb +39 -0
- data/spec/lib/processor_spec.rb +134 -0
- data/spec/lib/rack_middleware_spec.rb +103 -0
- data/spec/lib/response_spec.rb +53 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/sinatra_app.rb +54 -0
- metadata +148 -12
- data/lib/batch_api/error.rb +0 -3
- data/lib/batch_api/errors/base.rb +0 -45
- data/lib/batch_api/errors/operation.rb +0 -7
- data/lib/batch_api/errors/request.rb +0 -26
- data/lib/batch_api/processor/strategies/sequential.rb +0 -18
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The page you were looking for doesn't exist (404)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/404.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The page you were looking for doesn't exist.</h1>
|
23
|
+
<p>You may have mistyped the address or the page may have moved.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>The change you wanted was rejected (422)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/422.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>The change you wanted was rejected.</h1>
|
23
|
+
<p>Maybe you tried to change something you didn't have access to.</p>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>We're sorry, but something went wrong (500)</title>
|
5
|
+
<style type="text/css">
|
6
|
+
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
|
7
|
+
div.dialog {
|
8
|
+
width: 25em;
|
9
|
+
padding: 0 4em;
|
10
|
+
margin: 4em auto 0 auto;
|
11
|
+
border: 1px solid #ccc;
|
12
|
+
border-right-color: #999;
|
13
|
+
border-bottom-color: #999;
|
14
|
+
}
|
15
|
+
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
|
16
|
+
</style>
|
17
|
+
</head>
|
18
|
+
|
19
|
+
<body>
|
20
|
+
<!-- This file lives in public/500.html -->
|
21
|
+
<div class="dialog">
|
22
|
+
<h1>We're sorry, but something went wrong.</h1>
|
23
|
+
</div>
|
24
|
+
</body>
|
25
|
+
</html>
|
File without changes
|
@@ -0,0 +1,6 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
3
|
+
|
4
|
+
APP_PATH = File.expand_path('../../config/application', __FILE__)
|
5
|
+
require File.expand_path('../../config/boot', __FILE__)
|
6
|
+
require 'rails/commands'
|
@@ -0,0 +1,256 @@
|
|
1
|
+
shared_examples_for "integrating with a server" do
|
2
|
+
def headerize(hash)
|
3
|
+
Hash[hash.map do |k, v|
|
4
|
+
["HTTP_#{k.to_s.upcase}", v.to_s]
|
5
|
+
end]
|
6
|
+
end
|
7
|
+
|
8
|
+
before :all do
|
9
|
+
BatchApi.config.endpoint = "/batch"
|
10
|
+
BatchApi.config.verb = :post
|
11
|
+
end
|
12
|
+
|
13
|
+
before :each do
|
14
|
+
BatchApi::ErrorWrapper.stub(:expose_backtrace?).and_return(false)
|
15
|
+
end
|
16
|
+
|
17
|
+
# these are defined in the dummy app's endpoints controller
|
18
|
+
let(:get_headers) { {"foo" => "bar"} }
|
19
|
+
let(:get_params) { {"other" => "value" } }
|
20
|
+
|
21
|
+
let(:get_request) { {
|
22
|
+
url: "/endpoint",
|
23
|
+
method: "get",
|
24
|
+
headers: get_headers,
|
25
|
+
params: get_params
|
26
|
+
} }
|
27
|
+
|
28
|
+
let(:get_result) { {
|
29
|
+
status: 422,
|
30
|
+
body: {
|
31
|
+
"result" => "GET OK",
|
32
|
+
"params" => get_params.merge(
|
33
|
+
BatchApi.rails? ? {
|
34
|
+
"controller" => "endpoints",
|
35
|
+
"action" => "get"
|
36
|
+
} : {}
|
37
|
+
)
|
38
|
+
},
|
39
|
+
headers: { "GET" => "hello" }
|
40
|
+
} }
|
41
|
+
|
42
|
+
# these are defined in the dummy app's endpoints controller
|
43
|
+
let(:post_headers) { {"foo" => "bar"} }
|
44
|
+
let(:post_params) { {"other" => "value"} }
|
45
|
+
|
46
|
+
let(:post_request) { {
|
47
|
+
url: "/endpoint",
|
48
|
+
method: "post",
|
49
|
+
headers: post_headers,
|
50
|
+
params: post_params
|
51
|
+
} }
|
52
|
+
|
53
|
+
let(:post_result) { {
|
54
|
+
status: 203,
|
55
|
+
body: {
|
56
|
+
"result" => "POST OK",
|
57
|
+
"params" => post_params.merge(
|
58
|
+
BatchApi.rails? ? {
|
59
|
+
"controller" => "endpoints",
|
60
|
+
"action" => "post"
|
61
|
+
} : {}
|
62
|
+
)
|
63
|
+
},
|
64
|
+
headers: { "POST" => "guten tag" }
|
65
|
+
} }
|
66
|
+
|
67
|
+
let(:error_request) { {
|
68
|
+
url: "/endpoint/error",
|
69
|
+
method: "get"
|
70
|
+
} }
|
71
|
+
|
72
|
+
let(:error_response) { {
|
73
|
+
status: 500,
|
74
|
+
body: { "error" => { "message" => "StandardError" } }
|
75
|
+
} }
|
76
|
+
|
77
|
+
let(:missing_request) { {
|
78
|
+
url: "/dont/work",
|
79
|
+
method: "delete"
|
80
|
+
} }
|
81
|
+
|
82
|
+
let(:missing_response) { {
|
83
|
+
status: 404,
|
84
|
+
body: {}
|
85
|
+
} }
|
86
|
+
|
87
|
+
let(:parameter) {
|
88
|
+
(rand * 10000).to_i
|
89
|
+
}
|
90
|
+
|
91
|
+
let(:parameter_request) { {
|
92
|
+
url: "/endpoint/capture/#{parameter}",
|
93
|
+
method: "get"
|
94
|
+
} }
|
95
|
+
|
96
|
+
let(:parameter_result) { {
|
97
|
+
body: {
|
98
|
+
"result" => parameter.to_s
|
99
|
+
}
|
100
|
+
} }
|
101
|
+
|
102
|
+
let(:silent_request) { {
|
103
|
+
url: "/endpoint",
|
104
|
+
method: "post",
|
105
|
+
silent: true
|
106
|
+
} }
|
107
|
+
|
108
|
+
let(:failed_silent_request) {
|
109
|
+
error_request.merge(silent: true)
|
110
|
+
}
|
111
|
+
|
112
|
+
let(:failed_silent_result) {
|
113
|
+
error_response
|
114
|
+
}
|
115
|
+
|
116
|
+
before :each do
|
117
|
+
@t = Time.now
|
118
|
+
xhr :post, "/batch", {
|
119
|
+
ops: [
|
120
|
+
get_request,
|
121
|
+
post_request,
|
122
|
+
error_request,
|
123
|
+
missing_request,
|
124
|
+
parameter_request,
|
125
|
+
silent_request,
|
126
|
+
failed_silent_request
|
127
|
+
],
|
128
|
+
sequential: true
|
129
|
+
}.to_json, "CONTENT_TYPE" => "application/json"
|
130
|
+
end
|
131
|
+
|
132
|
+
it "returns a 200" do
|
133
|
+
response.status.should == 200
|
134
|
+
end
|
135
|
+
|
136
|
+
it "includes results" do
|
137
|
+
JSON.parse(response.body)["results"].should be_a(Array)
|
138
|
+
end
|
139
|
+
|
140
|
+
context "for a get request" do
|
141
|
+
describe "the response" do
|
142
|
+
before :each do
|
143
|
+
@result = JSON.parse(response.body)["results"][0]
|
144
|
+
end
|
145
|
+
|
146
|
+
it "returns the body as objects" do
|
147
|
+
@result = JSON.parse(response.body)["results"][0]
|
148
|
+
@result["body"].should == get_result[:body]
|
149
|
+
end
|
150
|
+
|
151
|
+
it "returns the expected status" do
|
152
|
+
@result["status"].should == get_result[:status]
|
153
|
+
end
|
154
|
+
|
155
|
+
it "returns the expected headers" do
|
156
|
+
@result["headers"].should include(get_result[:headers])
|
157
|
+
end
|
158
|
+
|
159
|
+
it "verifies that the right headers were received" do
|
160
|
+
@result["headers"]["REQUEST_HEADERS"].should include(
|
161
|
+
headerize(get_headers)
|
162
|
+
)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
context "for a request with parameters" do
|
168
|
+
describe "the response" do
|
169
|
+
before :each do
|
170
|
+
@result = JSON.parse(response.body)["results"][4]
|
171
|
+
end
|
172
|
+
|
173
|
+
it "properly parses the URL segment as a paramer" do
|
174
|
+
@result["body"].should == parameter_result[:body]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context "for a post request" do
|
180
|
+
describe "the response" do
|
181
|
+
before :each do
|
182
|
+
@result = JSON.parse(response.body)["results"][1]
|
183
|
+
end
|
184
|
+
|
185
|
+
it "returns the body raw if decode_json_responses = false" do
|
186
|
+
# BatchApi.config.decode_bodies = false
|
187
|
+
xhr :post, "/batch", {ops: [post_request], sequential: true}.to_json,
|
188
|
+
"CONTENT_TYPE" => "application/json"
|
189
|
+
@result = JSON.parse(response.body)["results"][0]
|
190
|
+
@result["body"].should == JSON.parse(post_result[:body].to_json)
|
191
|
+
end
|
192
|
+
|
193
|
+
it "returns the body as objects if decode_json_responses = true" do
|
194
|
+
@result["body"].should == post_result[:body]
|
195
|
+
end
|
196
|
+
|
197
|
+
it "returns the expected status" do
|
198
|
+
@result["status"].should == post_result[:status]
|
199
|
+
end
|
200
|
+
|
201
|
+
it "returns the expected headers" do
|
202
|
+
@result["headers"].should include(post_result[:headers])
|
203
|
+
end
|
204
|
+
|
205
|
+
it "verifies that the right headers were received" do
|
206
|
+
@result["headers"]["REQUEST_HEADERS"].should include(headerize(post_headers))
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context "for a request that returns an error" do
|
212
|
+
before :each do
|
213
|
+
@result = JSON.parse(response.body)["results"][2]
|
214
|
+
end
|
215
|
+
|
216
|
+
it "returns the right status" do
|
217
|
+
@result["status"].should == error_response[:status]
|
218
|
+
end
|
219
|
+
|
220
|
+
it "returns the right error information" do
|
221
|
+
# we don't care about the backtrace,
|
222
|
+
# the main thing is that the messsage arrives
|
223
|
+
@result["body"]["error"].should include(error_response[:body]["error"])
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context "for a request that returns error" do
|
228
|
+
before :each do
|
229
|
+
@result = JSON.parse(response.body)["results"][3]
|
230
|
+
end
|
231
|
+
|
232
|
+
it "returns the right status" do
|
233
|
+
@result["status"].should == 404
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context "for a silent request" do
|
238
|
+
before :each do
|
239
|
+
@result = JSON.parse(response.body)["results"][5]
|
240
|
+
end
|
241
|
+
|
242
|
+
it "returns nothing" do
|
243
|
+
@result.should == {}
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
context "for a silent request that causes an error" do
|
248
|
+
before :each do
|
249
|
+
@result = JSON.parse(response.body)["results"][6]
|
250
|
+
end
|
251
|
+
|
252
|
+
it "returns a regular result" do
|
253
|
+
@result.keys.should_not be_empty
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/sinatra_app'
|
3
|
+
require 'rack/test'
|
4
|
+
require_relative './shared_examples'
|
5
|
+
|
6
|
+
describe "Sinatra integration" do
|
7
|
+
include Rack::Test::Methods
|
8
|
+
|
9
|
+
def app
|
10
|
+
SinatraApp
|
11
|
+
end
|
12
|
+
|
13
|
+
it_should_behave_like "integrating with a server"
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'batch_api'
|
3
|
+
|
4
|
+
describe BatchApi do
|
5
|
+
describe ".config" do
|
6
|
+
it "has a reader for config" do
|
7
|
+
BatchApi.config.should_not be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it "provides a default config" do
|
11
|
+
BatchApi.config.should be_a(BatchApi::Configuration)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe ".rails?" do
|
16
|
+
it "returns a value we can't test based on whether Rails is defined" do
|
17
|
+
BatchApi.rails?.should_not be_nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BatchApi::Errors::BatchError do
|
4
|
+
|
5
|
+
[
|
6
|
+
BatchApi::Errors::OperationLimitExceeded,
|
7
|
+
BatchApi::Errors::BadOptionError,
|
8
|
+
BatchApi::Errors::NoOperationsError,
|
9
|
+
BatchApi::Errors::MalformedOperationError
|
10
|
+
].each do |klass|
|
11
|
+
it "provides a #{klass} error based on ArgumentError" do
|
12
|
+
klass.superclass.should == ArgumentError
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is is also a BatchError" do
|
16
|
+
klass.new.should be_a(BatchApi::Errors::BatchError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "has a status code of 422" do
|
20
|
+
klass.new.status_code.should == 422
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module BatchApi
|
4
|
+
describe Configuration do
|
5
|
+
let(:config) { Configuration.new }
|
6
|
+
|
7
|
+
describe "options" do
|
8
|
+
{
|
9
|
+
verb: :post,
|
10
|
+
endpoint: "/batch",
|
11
|
+
limit: 50,
|
12
|
+
batch_middleware: InternalMiddleware::DEFAULT_BATCH_MIDDLEWARE,
|
13
|
+
operation_middleware: InternalMiddleware::DEFAULT_OPERATION_MIDDLEWARE
|
14
|
+
}.each_pair do |option, default|
|
15
|
+
opt, defa = option, default
|
16
|
+
describe "##{opt}" do
|
17
|
+
it "has an accessor for #{opt}" do
|
18
|
+
stubby = stub
|
19
|
+
config.send("#{opt}=", stubby)
|
20
|
+
config.send(opt).should == stubby
|
21
|
+
end
|
22
|
+
|
23
|
+
it "defaults #{opt} to #{defa.inspect}" do
|
24
|
+
config.send(opt).should == defa
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|