batch_api2 0.3.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/MIT-LICENSE +20 -0
- data/Rakefile +30 -0
- data/changelog.md +74 -0
- data/lib/batch_api.rb +28 -0
- data/lib/batch_api/batch_error.rb +41 -0
- data/lib/batch_api/configuration.rb +36 -0
- 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 +28 -0
- data/lib/batch_api/internal_middleware/response_filter.rb +27 -0
- data/lib/batch_api/operation.rb +2 -0
- data/lib/batch_api/operation/rack.rb +76 -0
- data/lib/batch_api/operation/rails.rb +42 -0
- data/lib/batch_api/processor.rb +113 -0
- data/lib/batch_api/processor/executor.rb +18 -0
- data/lib/batch_api/processor/sequential.rb +29 -0
- data/lib/batch_api/rack_middleware.rb +37 -0
- data/lib/batch_api/response.rb +38 -0
- data/lib/batch_api/utils.rb +17 -0
- data/lib/batch_api/version.rb +3 -0
- data/lib/tasks/batch_api_tasks.rake +4 -0
- data/readme.md +243 -0
- data/spec/dummy/Gemfile +1 -0
- data/spec/dummy/Gemfile.lock +8 -0
- 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/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +32 -0
- data/spec/dummy/config/boot.rb +3 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +64 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/test.sqlite3 +0 -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/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 +44 -0
- data/spec/lib/internal_middleware/response_filter_spec.rb +61 -0
- data/spec/lib/internal_middleware_spec.rb +93 -0
- data/spec/lib/operation/rack_spec.rb +246 -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 +136 -0
- data/spec/lib/rack_middleware_spec.rb +103 -0
- data/spec/lib/response_spec.rb +53 -0
- data/spec/rack-integration/rails_spec.rb +10 -0
- data/spec/rack-integration/shared_examples.rb +273 -0
- data/spec/rack-integration/sinatra_integration_spec.rb +19 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/support/sinatra_app.rb +54 -0
- data/spec/support/sinatra_xhr.rb +13 -0
- metadata +214 -0
@@ -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,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
|
+
expect(BatchApi.config).not_to be_nil
|
8
|
+
end
|
9
|
+
|
10
|
+
it "provides a default config" do
|
11
|
+
expect(BatchApi.config).to 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
|
+
expect(BatchApi.rails?).not_to 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
|
+
expect(klass.superclass).to eq(ArgumentError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is is also a BatchError" do
|
16
|
+
expect(klass.new).to be_a(BatchApi::Errors::BatchError)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "has a status code of 422" do
|
20
|
+
expect(klass.new.status_code).to eq(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 = double
|
19
|
+
config.send("#{opt}=", stubby)
|
20
|
+
expect(config.send(opt)).to eq(stubby)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "defaults #{opt} to #{defa.inspect}" do
|
24
|
+
expect(config.send(opt)).to eq(defa)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'batch_api/error_wrapper'
|
3
|
+
|
4
|
+
describe BatchApi::ErrorWrapper do
|
5
|
+
let(:exception) {
|
6
|
+
StandardError.new(Faker::Lorem.words(3)).tap do |e|
|
7
|
+
e.set_backtrace(Kernel.caller)
|
8
|
+
end
|
9
|
+
}
|
10
|
+
|
11
|
+
let(:error) { BatchApi::ErrorWrapper.new(exception) }
|
12
|
+
|
13
|
+
describe "#body" do
|
14
|
+
it "includes the message in the body" do
|
15
|
+
expect(error.body[:error][:message]).to eq(exception.message)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "includes the backtrace if it should be there" do
|
19
|
+
allow(error).to receive(:expose_backtrace?).and_return(true)
|
20
|
+
expect(error.body[:error][:backtrace]).to eq(exception.backtrace)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "includes the backtrace if it should be there" do
|
24
|
+
allow(error).to receive(:expose_backtrace?).and_return(false)
|
25
|
+
expect(error.body[:backtrace]).to be_nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#render" do
|
30
|
+
it "returns the appropriate status" do
|
31
|
+
status = double
|
32
|
+
allow(error).to receive(:status_code).and_return(status)
|
33
|
+
expect(error.render[0]).to eq(status)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "returns appropriate content type" do
|
37
|
+
ctype = double
|
38
|
+
allow(BatchApi::RackMiddleware).to receive(:content_type).and_return(ctype)
|
39
|
+
expect(error.render[1]).to eq(ctype)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns the JSONified body as the 2nd" do
|
43
|
+
expect(error.render[2]).to eq([MultiJson.dump(error.body)])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#status_code" do
|
48
|
+
it "returns 500 by default" do
|
49
|
+
expect(error.status_code).to eq(500)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns another status code if the error supports that" do
|
53
|
+
err = StandardError.new
|
54
|
+
code = double
|
55
|
+
allow(err).to receive(:status_code).and_return(code)
|
56
|
+
expect(BatchApi::ErrorWrapper.new(err).status_code).to eq(code)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe ".expose_backtrace?" do
|
61
|
+
it "returns false if Rails.env.production?" do
|
62
|
+
allow(Rails).to receive(:env).and_return(double(test?: false, production?: true, development?: false))
|
63
|
+
expect(BatchApi::ErrorWrapper.expose_backtrace?).to be_falsey
|
64
|
+
allow(Rails.env).to receive(:production?).and_return(false)
|
65
|
+
expect(BatchApi::ErrorWrapper.expose_backtrace?).to be_truthy
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BatchApi::InternalMiddleware::DecodeJsonBody do
|
4
|
+
let(:app) { double("app", call: result) }
|
5
|
+
let(:decoder) { BatchApi::InternalMiddleware::DecodeJsonBody.new(app) }
|
6
|
+
let(:env) { double("env") }
|
7
|
+
let(:json) { {"data" => "is_json", "more" => {"hi" => "there"} } }
|
8
|
+
let(:result) {
|
9
|
+
BatchApi::Response.new([
|
10
|
+
200,
|
11
|
+
{"Content-Type" => "application/json"},
|
12
|
+
[MultiJson.dump(json)]
|
13
|
+
])
|
14
|
+
}
|
15
|
+
|
16
|
+
describe "#call" do
|
17
|
+
context "for json results" do
|
18
|
+
it "decodes JSON results for application/json responses" do
|
19
|
+
result = decoder.call(env)
|
20
|
+
expect(result.body).to eq(json)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "doesn't change anything else" do
|
24
|
+
result = decoder.call(env)
|
25
|
+
expect(result.status).to eq(200)
|
26
|
+
expect(result.headers).to eq({"Content-Type" => "application/json"})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "for non-JSON responses" do
|
31
|
+
it "doesn't decode" do
|
32
|
+
result.headers = {"Content-Type" => "text/html"}
|
33
|
+
expect(decoder.call(env).body).to eq(MultiJson.dump(json))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "for empty responses" do
|
38
|
+
it "doesn't try to parse" do
|
39
|
+
result.body = ""
|
40
|
+
expect(decoder.call(env).body).to eq("")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BatchApi::InternalMiddleware::ResponseFilter do
|
4
|
+
let(:app) { double("app", call: result) }
|
5
|
+
let(:surpressor) { BatchApi::InternalMiddleware::ResponseFilter.new(app) }
|
6
|
+
let(:env) { {
|
7
|
+
op: double("operation", options: {"silent" => true})
|
8
|
+
} }
|
9
|
+
|
10
|
+
let(:result) {
|
11
|
+
BatchApi::Response.new([
|
12
|
+
200,
|
13
|
+
{"Content-Type" => "application/json"},
|
14
|
+
["{}"]
|
15
|
+
])
|
16
|
+
}
|
17
|
+
|
18
|
+
describe "#call" do
|
19
|
+
context "for results with silent" do
|
20
|
+
context "for successful (200-299) results" do
|
21
|
+
it "empties the response so its as_json is empty" do
|
22
|
+
surpressor.call(env)
|
23
|
+
expect(result.as_json).to eq({})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "for non-successful responses" do
|
28
|
+
it "doesn't change anything else" do
|
29
|
+
result.status = 301
|
30
|
+
expect {
|
31
|
+
surpressor.call(env)
|
32
|
+
}.not_to change(result, :to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "for results without silent" do
|
38
|
+
before :each do
|
39
|
+
env[:op].options[:silent] = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
context "for successful (200-299) results" do
|
43
|
+
it "does nothing" do
|
44
|
+
expect {
|
45
|
+
surpressor.call(env)
|
46
|
+
}.not_to change(result, :to_s)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "for non-successful responses" do
|
51
|
+
it "doesn't change anything else" do
|
52
|
+
result.status = 301
|
53
|
+
expect {
|
54
|
+
surpressor.call(env)
|
55
|
+
}.not_to change(result, :to_s)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BatchApi::InternalMiddleware do
|
4
|
+
class FakeBuilder
|
5
|
+
attr_accessor :middlewares
|
6
|
+
|
7
|
+
def initialize(&block)
|
8
|
+
@middlewares = []
|
9
|
+
instance_eval(&block) if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
def use(middleware, *args)
|
13
|
+
@middlewares << [middleware, args]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:builder) { FakeBuilder.new }
|
18
|
+
|
19
|
+
it "builds an empty default global middleware" do
|
20
|
+
builder.instance_eval(
|
21
|
+
&BatchApi::InternalMiddleware::DEFAULT_BATCH_MIDDLEWARE
|
22
|
+
)
|
23
|
+
expect(builder.middlewares).to be_empty
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "internal middleware defaults" do
|
27
|
+
before :each do
|
28
|
+
builder.instance_eval(
|
29
|
+
&BatchApi::InternalMiddleware::DEFAULT_OPERATION_MIDDLEWARE
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "builds a per-op middleware with the response silencer" do
|
34
|
+
expect(builder.middlewares[0]).to eq(
|
35
|
+
[BatchApi::InternalMiddleware::ResponseFilter, []]
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "builds a per-op middleware with the JSON decoder" do
|
40
|
+
expect(builder.middlewares[1]).to eq(
|
41
|
+
[BatchApi::InternalMiddleware::DecodeJsonBody, []]
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe ".batch_stack" do
|
47
|
+
# we can't use stubs inside the procs since they're instance_eval'd
|
48
|
+
let(:global_config) { Proc.new { use "Global" } }
|
49
|
+
let(:strategy) { double("strategy") }
|
50
|
+
let(:processor) { double("processor", strategy: strategy) }
|
51
|
+
let(:stack) { BatchApi::InternalMiddleware.batch_stack(processor) }
|
52
|
+
|
53
|
+
before :each do
|
54
|
+
allow(BatchApi.config).to receive(:batch_middleware).and_return(global_config)
|
55
|
+
stub_const("Middleware::Builder", FakeBuilder)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "builds the stack with the right number of wares" do
|
59
|
+
expect(stack.middlewares.length).to eq(2)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "builds a middleware stack starting with the configured global wares" do
|
63
|
+
expect(stack.middlewares[0].first).to eq("Global")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "inserts the appropriate strategy from the processor" do
|
67
|
+
expect(stack.middlewares[1].first).to eq(strategy)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe ".operation_stack" do
|
72
|
+
# we can't use stubs inside the procs since they're instance_eval'd
|
73
|
+
let(:op_config) { Proc.new { use "Op" } }
|
74
|
+
let(:stack) { BatchApi::InternalMiddleware.operation_stack }
|
75
|
+
|
76
|
+
before :each do
|
77
|
+
allow(BatchApi.config).to receive(:operation_middleware).and_return(op_config)
|
78
|
+
stub_const("Middleware::Builder", FakeBuilder)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "builds the stack with the right number of wares" do
|
82
|
+
expect(stack.middlewares.length).to eq(2)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "builds a middleware stack including the configured per-op wares" do
|
86
|
+
expect(stack.middlewares[0].first).to eq("Op")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "builds a middleware stack ending with the executor" do
|
90
|
+
expect(stack.middlewares[1].first).to eq(BatchApi::Processor::Executor)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'batch_api/operation'
|
3
|
+
|
4
|
+
describe BatchApi::Operation::Rack do
|
5
|
+
let(:op_params) { {
|
6
|
+
"method" => "POST",
|
7
|
+
# this matches a route in our dummy application
|
8
|
+
"url" => "/endpoint?foo=baz",
|
9
|
+
"params" => {a: 2},
|
10
|
+
"headers" => {"foo" => "bar"}
|
11
|
+
} }
|
12
|
+
|
13
|
+
# for env, see bottom of file - it's long
|
14
|
+
let(:operation) { BatchApi::Operation::Rack.new(op_params, env, app) }
|
15
|
+
let(:app) { double("application", call: [200, {}, ["foo"]]) }
|
16
|
+
|
17
|
+
describe "accessors" do
|
18
|
+
[
|
19
|
+
:method, :url, :params, :headers,
|
20
|
+
:env, :app, :result, :options
|
21
|
+
].each do |a|
|
22
|
+
attr = a
|
23
|
+
it "has an accessor for #{attr}" do
|
24
|
+
value = double
|
25
|
+
operation.send("#{attr}=", value)
|
26
|
+
expect(operation.send(attr)).to eq(value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#initialize" do
|
32
|
+
["method", "url", "params", "headers"].each do |a|
|
33
|
+
attr = a
|
34
|
+
it "extracts the #{attr} information from the operation params" do
|
35
|
+
expect(operation.send(attr)).to eq(op_params[attr])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "sets options to the op" do
|
40
|
+
expect(operation.options).to eq(op_params)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "defaults method to get if not provided" do
|
44
|
+
op = BatchApi::Operation::Rack.new(op_params.except("method"), env, app)
|
45
|
+
expect(op.method).to eq("get")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "defaults params to {} if not provided" do
|
49
|
+
op = BatchApi::Operation::Rack.new(op_params.except("params"), env, app)
|
50
|
+
expect(op.params).to eq({})
|
51
|
+
end
|
52
|
+
|
53
|
+
it "defaults headers to {} if not provided" do
|
54
|
+
op = BatchApi::Operation::Rack.new(op_params.except("headers"), env, app)
|
55
|
+
expect(op.headers).to eq({})
|
56
|
+
end
|
57
|
+
|
58
|
+
it "does a deep dup of the env" do
|
59
|
+
expect(operation.env).to eq(env)
|
60
|
+
|
61
|
+
flat_env = env.to_a.flatten
|
62
|
+
operation.env.to_a.flatten.each_with_index do |obj, index|
|
63
|
+
# this is a rough test for deep dup -- make sure the objects
|
64
|
+
# that aren't symbols aren't actually the same objects in memory
|
65
|
+
if obj.is_a?(Hash) || obj.is_a?(Array)
|
66
|
+
expect(obj.object_id).not_to eq(flat_env[index].object_id)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "raises a MalformedOperationError if URL is missing" do
|
72
|
+
no_url = op_params.dup.tap {|o| o.delete("url") }
|
73
|
+
expect {
|
74
|
+
BatchApi::Operation::Rack.new(no_url, env, app)
|
75
|
+
}.to raise_exception(BatchApi::Errors::MalformedOperationError)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#process_env" do
|
80
|
+
let(:processed_env) { operation.tap {|o| o.process_env}.env }
|
81
|
+
|
82
|
+
before { BatchApi.config.stub(endpoint: '/api/batch') }
|
83
|
+
|
84
|
+
it "merges any headers in in the right format" do
|
85
|
+
key = "HTTP_FOO" # as defined above in op_params
|
86
|
+
|
87
|
+
expect(processed_env[key]).not_to eq(env[key])
|
88
|
+
# in this case, it's a batch controller
|
89
|
+
expect(processed_env[key]).to eq(op_params["headers"]["foo"])
|
90
|
+
end
|
91
|
+
|
92
|
+
it "preserves existing headers" do
|
93
|
+
expect(processed_env["HTTP_PREVIOUS_HEADERS"]).to eq(env["HTTP_PREVIOUS_HEADERS"])
|
94
|
+
end
|
95
|
+
|
96
|
+
it "updates the method" do
|
97
|
+
key = "REQUEST_METHOD"
|
98
|
+
expect(processed_env[key]).not_to eq(env[key])
|
99
|
+
expect(processed_env[key]).to eq("POST")
|
100
|
+
end
|
101
|
+
|
102
|
+
it "updates the REQUEST_URI" do
|
103
|
+
key = "REQUEST_URI"
|
104
|
+
expect(processed_env[key]).not_to eq(env[key])
|
105
|
+
expect(processed_env[key]).to eq("http://localhost:3000#{op_params["url"]}")
|
106
|
+
end
|
107
|
+
|
108
|
+
it "works if REQUEST_URI is blank" do
|
109
|
+
key = "REQUEST_URI"
|
110
|
+
env.delete(key)
|
111
|
+
expect(processed_env[key]).to be_nil
|
112
|
+
end
|
113
|
+
|
114
|
+
it "updates the REQUEST_PATH with the path component (w/o params)" do
|
115
|
+
key = "REQUEST_PATH"
|
116
|
+
expect(processed_env[key]).not_to eq(env[key])
|
117
|
+
expect(processed_env[key]).to eq(op_params["url"].split("?").first)
|
118
|
+
end
|
119
|
+
|
120
|
+
it "updates the original fullpath" do
|
121
|
+
key = "ORIGINAL_FULLPATH"
|
122
|
+
expect(processed_env[key]).not_to eq(env[key])
|
123
|
+
expect(processed_env[key]).to eq(op_params["url"])
|
124
|
+
end
|
125
|
+
|
126
|
+
it "updates the PATH_INFO" do
|
127
|
+
key = "PATH_INFO"
|
128
|
+
expect(processed_env[key]).not_to eq(env[key])
|
129
|
+
expect(processed_env[key]).to eq(op_params["url"])
|
130
|
+
end
|
131
|
+
|
132
|
+
it "updates the rack query string" do
|
133
|
+
key = "rack.request.query_string"
|
134
|
+
expect(processed_env[key]).not_to eq(env[key])
|
135
|
+
expect(processed_env[key]).to eq(op_params["url"].split("?").last)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "updates the QUERY_STRING" do
|
139
|
+
key = "QUERY_STRING"
|
140
|
+
expect(processed_env[key]).not_to eq(env[key])
|
141
|
+
expect(processed_env[key]).to eq(op_params["url"].split("?").last)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "updates the form hash" do
|
145
|
+
key = "rack.request.form_hash"
|
146
|
+
expect(processed_env[key]).not_to eq(env[key])
|
147
|
+
expect(processed_env[key]).to eq(op_params["params"])
|
148
|
+
end
|
149
|
+
|
150
|
+
context "query_hash" do
|
151
|
+
it "sets it to params for a GET" do
|
152
|
+
operation.method = "get"
|
153
|
+
processed_env = operation.tap {|o| o.process_env}.env
|
154
|
+
key = "rack.request.query_hash"
|
155
|
+
expect(processed_env[key]).not_to eq(env[key])
|
156
|
+
expect(processed_env[key]).to eq(op_params["params"])
|
157
|
+
end
|
158
|
+
|
159
|
+
it "sets it to nil for a POST" do
|
160
|
+
key = "rack.request.query_hash"
|
161
|
+
expect(processed_env[key]).not_to eq(env[key])
|
162
|
+
expect(processed_env[key]).to be_nil
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "#execute" do
|
168
|
+
context "when it works" do
|
169
|
+
let(:result) { [
|
170
|
+
200,
|
171
|
+
{header: "footer"},
|
172
|
+
double(body: "{\"data\":2}", cookies: nil)
|
173
|
+
] }
|
174
|
+
let(:processed_env) { double }
|
175
|
+
|
176
|
+
before :each do
|
177
|
+
allow(operation).to receive(:process_env) { operation.env = processed_env }
|
178
|
+
end
|
179
|
+
|
180
|
+
it "executes the call with the application" do
|
181
|
+
expect(app).to receive(:call).with(processed_env)
|
182
|
+
operation.execute
|
183
|
+
end
|
184
|
+
|
185
|
+
it "returns a BatchAPI::Response made from the result" do
|
186
|
+
response = double
|
187
|
+
allow(app).to receive(:call).and_return(result)
|
188
|
+
expect(BatchApi::Response).to receive(:new).with(result).and_return(response)
|
189
|
+
expect(operation.execute).to eq(response)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "returns a BatchApi::Response from an ErrorWrapper for errors" do
|
193
|
+
err = StandardError.new
|
194
|
+
result, rendered, response = double, double, double
|
195
|
+
b_err = double("batch error", render: rendered)
|
196
|
+
|
197
|
+
# simulate the error
|
198
|
+
allow(app).to receive(:call).and_raise(err)
|
199
|
+
# we'll create the BatchError
|
200
|
+
expect(BatchApi::ErrorWrapper).to receive(:new).with(err).and_return(b_err)
|
201
|
+
# render that as the response
|
202
|
+
expect(BatchApi::Response).to receive(:new).with(rendered).and_return(response)
|
203
|
+
# and return the response overall
|
204
|
+
expect(operation.execute).to eq(response)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
let(:env) {
|
210
|
+
{
|
211
|
+
"CONTENT_LENGTH"=>"10",
|
212
|
+
"CONTENT_TYPE"=>"application/x-www-form-urlencoded",
|
213
|
+
"GATEWAY_INTERFACE"=>"CGI/1.1",
|
214
|
+
"PATH_INFO"=>"/foo",
|
215
|
+
"QUERY_STRING"=>"",
|
216
|
+
"REMOTE_ADDR"=>"127.0.0.1",
|
217
|
+
"REMOTE_HOST"=>"1035.spotilocal.com",
|
218
|
+
"REQUEST_METHOD"=>"REPORT",
|
219
|
+
"REQUEST_URI"=>"http://localhost:3000/api/batch",
|
220
|
+
"SCRIPT_NAME"=>"",
|
221
|
+
"SERVER_NAME"=>"localhost",
|
222
|
+
"SERVER_PORT"=>"3000",
|
223
|
+
"SERVER_PROTOCOL"=>"HTTP/1.1",
|
224
|
+
"SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.3/2012-02-16)",
|
225
|
+
"HTTP_USER_AGENT"=>"curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5",
|
226
|
+
"HTTP_HOST"=>"localhost:3000",
|
227
|
+
"HTTP_ACCEPT"=>"*/*",
|
228
|
+
"HTTP_PREVIOUS_HEADERS" => "value",
|
229
|
+
"rack.version"=>[1,1],
|
230
|
+
"rack.input"=>StringIO.new("{\"ops\":{}}"),
|
231
|
+
"rack.errors"=>$stderr,
|
232
|
+
"rack.multithread"=>false,
|
233
|
+
"rack.multiprocess"=>false,
|
234
|
+
"rack.run_once"=>false,
|
235
|
+
"rack.url_scheme"=>"http",
|
236
|
+
"HTTP_VERSION"=>"HTTP/1.1",
|
237
|
+
"REQUEST_PATH"=>"/api/batch",
|
238
|
+
"ORIGINAL_FULLPATH"=>"/api/batch",
|
239
|
+
"rack.request.form_input"=>StringIO.new("{\"ops\":{}}"),
|
240
|
+
"rack.request.form_hash"=>{"{\"ops\":{}}"=>nil},
|
241
|
+
"rack.request.form_vars"=>"{\"ops\":{}}",
|
242
|
+
"rack.request.query_string"=>"",
|
243
|
+
"rack.request.query_hash"=>{}
|
244
|
+
}
|
245
|
+
}
|
246
|
+
end
|