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.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +30 -0
  4. data/changelog.md +74 -0
  5. data/lib/batch_api.rb +28 -0
  6. data/lib/batch_api/batch_error.rb +41 -0
  7. data/lib/batch_api/configuration.rb +36 -0
  8. data/lib/batch_api/error_wrapper.rb +44 -0
  9. data/lib/batch_api/internal_middleware.rb +87 -0
  10. data/lib/batch_api/internal_middleware/decode_json_body.rb +28 -0
  11. data/lib/batch_api/internal_middleware/response_filter.rb +27 -0
  12. data/lib/batch_api/operation.rb +2 -0
  13. data/lib/batch_api/operation/rack.rb +76 -0
  14. data/lib/batch_api/operation/rails.rb +42 -0
  15. data/lib/batch_api/processor.rb +113 -0
  16. data/lib/batch_api/processor/executor.rb +18 -0
  17. data/lib/batch_api/processor/sequential.rb +29 -0
  18. data/lib/batch_api/rack_middleware.rb +37 -0
  19. data/lib/batch_api/response.rb +38 -0
  20. data/lib/batch_api/utils.rb +17 -0
  21. data/lib/batch_api/version.rb +3 -0
  22. data/lib/tasks/batch_api_tasks.rake +4 -0
  23. data/readme.md +243 -0
  24. data/spec/dummy/Gemfile +1 -0
  25. data/spec/dummy/Gemfile.lock +8 -0
  26. data/spec/dummy/README.rdoc +261 -0
  27. data/spec/dummy/Rakefile +15 -0
  28. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  29. data/spec/dummy/app/assets/javascripts/endpoints.js +2 -0
  30. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  31. data/spec/dummy/app/assets/stylesheets/endpoints.css +4 -0
  32. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  33. data/spec/dummy/app/controllers/endpoints_controller.rb +36 -0
  34. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  35. data/spec/dummy/app/helpers/endpoints_helper.rb +2 -0
  36. data/spec/dummy/app/views/endpoints/get.html.erb +2 -0
  37. data/spec/dummy/app/views/endpoints/post.html.erb +2 -0
  38. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  39. data/spec/dummy/bin/bundle +3 -0
  40. data/spec/dummy/bin/rails +4 -0
  41. data/spec/dummy/bin/rake +4 -0
  42. data/spec/dummy/bin/setup +29 -0
  43. data/spec/dummy/config.ru +4 -0
  44. data/spec/dummy/config/application.rb +32 -0
  45. data/spec/dummy/config/boot.rb +3 -0
  46. data/spec/dummy/config/database.yml +25 -0
  47. data/spec/dummy/config/environment.rb +5 -0
  48. data/spec/dummy/config/environments/development.rb +41 -0
  49. data/spec/dummy/config/environments/production.rb +79 -0
  50. data/spec/dummy/config/environments/test.rb +42 -0
  51. data/spec/dummy/config/initializers/assets.rb +11 -0
  52. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  53. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  54. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  55. data/spec/dummy/config/initializers/inflections.rb +16 -0
  56. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  57. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  58. data/spec/dummy/config/initializers/session_store.rb +3 -0
  59. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  60. data/spec/dummy/config/locales/en.yml +23 -0
  61. data/spec/dummy/config/routes.rb +64 -0
  62. data/spec/dummy/config/secrets.yml +22 -0
  63. data/spec/dummy/db/test.sqlite3 +0 -0
  64. data/spec/dummy/public/404.html +26 -0
  65. data/spec/dummy/public/422.html +26 -0
  66. data/spec/dummy/public/500.html +25 -0
  67. data/spec/dummy/public/favicon.ico +0 -0
  68. data/spec/dummy/script/rails +6 -0
  69. data/spec/dummy/test/functional/endpoints_controller_test.rb +14 -0
  70. data/spec/dummy/test/unit/helpers/endpoints_helper_test.rb +4 -0
  71. data/spec/lib/batch_api_spec.rb +20 -0
  72. data/spec/lib/batch_error_spec.rb +23 -0
  73. data/spec/lib/configuration_spec.rb +30 -0
  74. data/spec/lib/error_wrapper_spec.rb +68 -0
  75. data/spec/lib/internal_middleware/decode_json_body_spec.rb +44 -0
  76. data/spec/lib/internal_middleware/response_filter_spec.rb +61 -0
  77. data/spec/lib/internal_middleware_spec.rb +93 -0
  78. data/spec/lib/operation/rack_spec.rb +246 -0
  79. data/spec/lib/operation/rails_spec.rb +100 -0
  80. data/spec/lib/processor/executor_spec.rb +22 -0
  81. data/spec/lib/processor/sequential_spec.rb +39 -0
  82. data/spec/lib/processor_spec.rb +136 -0
  83. data/spec/lib/rack_middleware_spec.rb +103 -0
  84. data/spec/lib/response_spec.rb +53 -0
  85. data/spec/rack-integration/rails_spec.rb +10 -0
  86. data/spec/rack-integration/shared_examples.rb +273 -0
  87. data/spec/rack-integration/sinatra_integration_spec.rb +19 -0
  88. data/spec/spec_helper.rb +42 -0
  89. data/spec/support/sinatra_app.rb +54 -0
  90. data/spec/support/sinatra_xhr.rb +13 -0
  91. 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,14 @@
1
+ require 'test_helper'
2
+
3
+ class EndpointsControllerTest < ActionController::TestCase
4
+ test "should get get" do
5
+ get :get
6
+ assert_response :success
7
+ end
8
+
9
+ test "should get post" do
10
+ get :post
11
+ assert_response :success
12
+ end
13
+
14
+ end
@@ -0,0 +1,4 @@
1
+ require 'test_helper'
2
+
3
+ class EndpointsHelperTest < ActionView::TestCase
4
+ 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
+ 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