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,100 @@
1
+ require 'spec_helper'
2
+ require 'batch_api/operation'
3
+
4
+ describe BatchApi::Operation::Rails 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::Rails.new(op_params, env, app) }
15
+ let(:app) { double("application", call: [200, {}, ["foo"]]) }
16
+ let(:path_params) { {controller: "batch_api/batch", action: "batch"} }
17
+ let(:mixed_params) { op_params["params"].merge(path_params) }
18
+
19
+ before :each do
20
+ allow(::Rails.application.routes).to receive(:recognize_path).and_return(path_params)
21
+ end
22
+
23
+ describe "#initialize" do
24
+ it "merges in the Rails path params" do
25
+ expect(::Rails.application.routes).to receive(:recognize_path).with(
26
+ op_params["url"],
27
+ op_params
28
+ ).and_return(path_params)
29
+
30
+ expect(operation.params).to include(path_params)
31
+ end
32
+
33
+ it "doesn't change the params if the path isn't recognized" do
34
+ allow(::Rails.application.routes).to receive(:recognize_path).and_raise(StandardError)
35
+ expect(operation.params).to eq(op_params["params"])
36
+ end
37
+ end
38
+
39
+ describe "#process_env" do
40
+ let(:processed_env) { operation.tap {|o| o.process_env}.env }
41
+
42
+ it "updates the ActionDispatch params" do
43
+ key = "action_dispatch.request.parameters"
44
+ expect(processed_env[key]).not_to eq(env[key])
45
+ expect(processed_env[key]).to eq(mixed_params)
46
+ end
47
+
48
+ it "updates the ActionDispatch request params" do
49
+ key = "action_dispatch.request.request_parameters"
50
+ expect(processed_env[key]).not_to eq(env[key])
51
+ expect(processed_env[key]).to eq(mixed_params)
52
+ end
53
+ end
54
+
55
+ let(:env) {
56
+ {
57
+ "CONTENT_LENGTH"=>"10",
58
+ "CONTENT_TYPE"=>"application/x-www-form-urlencoded",
59
+ "GATEWAY_INTERFACE"=>"CGI/1.1",
60
+ "PATH_INFO"=>"/foo",
61
+ "QUERY_STRING"=>"",
62
+ "REMOTE_ADDR"=>"127.0.0.1",
63
+ "REMOTE_HOST"=>"1035.spotilocal.com",
64
+ "REQUEST_METHOD"=>"REPORT",
65
+ "REQUEST_URI"=>"http://localhost:3000/batch",
66
+ "SCRIPT_NAME"=>"",
67
+ "SERVER_NAME"=>"localhost",
68
+ "SERVER_PORT"=>"3000",
69
+ "SERVER_PROTOCOL"=>"HTTP/1.1",
70
+ "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.3/2012-02-16)",
71
+ "HTTP_USER_AGENT"=>"curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5",
72
+ "HTTP_HOST"=>"localhost:3000",
73
+ "HTTP_ACCEPT"=>"*/*",
74
+ "HTTP_PREVIOUS_HEADERS" => "value",
75
+ "rack.version"=>[1,1],
76
+ "rack.input"=>StringIO.new("{\"ops\":{}}"),
77
+ "rack.errors"=>$stderr,
78
+ "rack.multithread"=>false,
79
+ "rack.multiprocess"=>false,
80
+ "rack.run_once"=>false,
81
+ "rack.url_scheme"=>"http",
82
+ "HTTP_VERSION"=>"HTTP/1.1",
83
+ "REQUEST_PATH"=>"/batch",
84
+ "ORIGINAL_FULLPATH"=>"/batch",
85
+ "action_dispatch.routes"=>Rails.application.routes,
86
+ "action_dispatch.parameter_filter"=>[:password],
87
+ "action_dispatch.secret_token"=>"fc6fbc81b3204410da8389",
88
+ "action_dispatch.show_exceptions"=>true,
89
+ "action_dispatch.show_detailed_exceptions"=>true,
90
+ "action_dispatch.logger"=>Rails.logger,
91
+ "action_dispatch.backtrace_cleaner"=>nil,
92
+ "action_dispatch.request_id"=>"2e7c988bea73e13dca4fac059a1bb187",
93
+ "action_dispatch.remote_ip"=>"127.0.0.1",
94
+ "action_dispatch.request.content_type"=>"application/x-www-form-urlencoded",
95
+ "action_dispatch.request.path_parameters"=> {},
96
+ "rack.request.query_string"=>"",
97
+ "rack.request.query_hash"=>{}
98
+ }
99
+ }
100
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'batch_api/processor/executor'
3
+
4
+ describe BatchApi::Processor::Executor do
5
+
6
+ let(:app) { double("app", call: double) }
7
+ let(:executor) { BatchApi::Processor::Executor.new(app) }
8
+ let(:result) { double("result") }
9
+ let(:op) { double("operation", execute: result) }
10
+ let(:env) { {op: op} }
11
+
12
+ describe "#call" do
13
+ it "executes the operation" do
14
+ expect(op).to receive(:execute)
15
+ executor.call(env)
16
+ end
17
+
18
+ it "returns the result" do
19
+ expect(executor.call(env)).to eq(result)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe BatchApi::Processor::Sequential do
4
+
5
+ let(:app) { double("app", call: double) }
6
+ let(:sequential) { BatchApi::Processor::Sequential.new(app) }
7
+
8
+ describe "#call" do
9
+ let(:call_results) { 3.times.collect {|i| double("called #{i}") } }
10
+ let(:env) { {
11
+ ops: 3.times.collect {|i| double("op #{i}") }
12
+ } }
13
+ let(:op_middleware) { double("middleware", call: {}) }
14
+
15
+ before :each do
16
+ allow(BatchApi::InternalMiddleware).
17
+ to receive(:operation_stack).and_return(op_middleware)
18
+ allow(op_middleware).to receive(:call).and_return(*call_results)
19
+ end
20
+
21
+ it "creates an operation middleware stack and calls it for each op" do
22
+ env[:ops].each {|op|
23
+ expect(op_middleware).to receive(:call).
24
+ with(hash_including(op: op)).ordered
25
+ }
26
+ sequential.call(env)
27
+ end
28
+
29
+ it "includes the rest of the env in the calls" do
30
+ expect(op_middleware).to receive(:call).
31
+ with(hash_including(env)).exactly(3).times
32
+ sequential.call(env)
33
+ end
34
+
35
+ it "returns the results of the calls" do
36
+ expect(sequential.call(env)).to eq(call_results)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+
3
+ describe BatchApi::Processor do
4
+
5
+ let(:ops) { [ {"url" => "/endpoint", "method" => "get"} ] }
6
+ let(:options) { { "sequential" => true } }
7
+ let(:env) { {
8
+ "CONTENT_TYPE"=>"application/x-www-form-urlencoded",
9
+ "GATEWAY_INTERFACE"=>"CGI/1.1",
10
+ "PATH_INFO"=>"/foo",
11
+ "QUERY_STRING"=>"",
12
+ "REMOTE_ADDR"=>"127.0.0.1",
13
+ "REMOTE_HOST"=>"1035.spotilocal.com",
14
+ "REQUEST_METHOD"=>"REPORT",
15
+ "REQUEST_URI"=>"http://localhost:3000/batch",
16
+ "SCRIPT_NAME"=>"",
17
+ "rack.input" => StringIO.new,
18
+ "rack.errors" => StringIO.new,
19
+ "SERVER_NAME"=>"localhost",
20
+ "SERVER_PORT"=>"3000",
21
+ "SERVER_PROTOCOL"=>"HTTP/1.1",
22
+ "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.3/2012-02-16)",
23
+ "HTTP_USER_AGENT"=>"curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21",
24
+ "HTTP_HOST"=>"localhost:3000"
25
+ } }
26
+
27
+ let(:request) {
28
+ Rack::Request.new(env).tap do |r|
29
+ allow(r).to receive(:params).and_return({}.merge("ops" => ops).merge(options))
30
+ end
31
+ }
32
+ let(:app) { double("application", call: [200, {}, ["foo"]]) }
33
+ let(:processor) { BatchApi::Processor.new(request, app) }
34
+
35
+ describe "#initialize" do
36
+ # this may be brittle...consider refactoring?
37
+ it "turns the ops params into processed operations at #ops" do
38
+ # simulate receiving several operations
39
+ klass = double("op class")
40
+ allow(BatchApi::Processor).to receive(:operation_klass).and_return(klass)
41
+ operation_objects = 3.times.collect { double("operation object") }
42
+ operation_params = 3.times.collect do |i|
43
+ double("raw operation").tap do |o|
44
+ expect(klass).to receive(:new)
45
+ .with(o, env, app).and_return(operation_objects[i])
46
+ end
47
+ end
48
+
49
+ request.params["ops"] = operation_params
50
+ expect(BatchApi::Processor.new(request, app).ops).to eq(operation_objects)
51
+ end
52
+
53
+ it "makes the options available" do
54
+ expect(BatchApi::Processor.new(request, app).options).to eq(options)
55
+ end
56
+
57
+ it "makes the app available" do
58
+ expect(BatchApi::Processor.new(request, app).app).to eq(app)
59
+ end
60
+
61
+ context "error conditions" do
62
+ it "(currently) throws an error if sequential is not true" do
63
+ request.params.delete("sequential")
64
+ expect {
65
+ BatchApi::Processor.new(request, app)
66
+ }.to raise_exception(BatchApi::Errors::BadOptionError)
67
+ end
68
+
69
+ it "raise a OperationLimitExceeded error if too many ops provided" do
70
+ ops = (BatchApi.config.limit + 1).to_i.times.collect {|i| i}
71
+ request.params["ops"] = ops
72
+ expect {
73
+ BatchApi::Processor.new(request, app)
74
+ }.to raise_exception(BatchApi::Errors::OperationLimitExceeded)
75
+ end
76
+
77
+ it "raises a NoOperationError if operations.blank?" do
78
+ request.params["ops"] = nil
79
+ expect {
80
+ BatchApi::Processor.new(request, app)
81
+ }.to raise_exception(BatchApi::Errors::NoOperationsError)
82
+ request.params["ops"] = []
83
+ expect {
84
+ BatchApi::Processor.new(request, app)
85
+ }.to raise_exception(BatchApi::Errors::NoOperationsError)
86
+ end
87
+ end
88
+ end
89
+
90
+ describe "#strategy" do
91
+ it "returns BatchApi::Processor::Sequential" do
92
+ expect(processor.strategy).to eq(BatchApi::Processor::Sequential)
93
+ end
94
+ end
95
+
96
+ describe "#execute!" do
97
+ let(:result) { double("result") }
98
+ let(:stack) { double("stack", call: result) }
99
+ let(:middleware_env) { {
100
+ ops: processor.ops, # the processed Operation objects
101
+ rack_env: env,
102
+ rack_app: app,
103
+ options: options
104
+ } }
105
+
106
+ before :each do
107
+ allow(BatchApi::InternalMiddleware).to receive(:batch_stack).and_return(stack)
108
+ end
109
+
110
+ it "calls an internal middleware stacks with the appropriate data" do
111
+ expect(stack).to receive(:call).with(middleware_env)
112
+ processor.execute!
113
+ end
114
+
115
+ it "returns the formatted result of the strategy" do
116
+ allow(stack).to receive(:call).and_return(stubby = double)
117
+ expect(processor.execute!["results"]).to eq(stubby)
118
+ end
119
+ end
120
+
121
+ describe ".operation_klass" do
122
+ it "returns BatchApi::Operation::Rack if !Rails" do
123
+ allow(BatchApi).to receive(:rails?).and_return(false)
124
+ expect(BatchApi::Processor.operation_klass).to eq(
125
+ BatchApi::Operation::Rack
126
+ )
127
+ end
128
+
129
+ it "returns BatchApi::Operation::Rails if Rails" do
130
+ allow(BatchApi).to receive(:rails?).and_return(true)
131
+ expect(BatchApi::Processor.operation_klass).to eq(
132
+ BatchApi::Operation::Rails
133
+ )
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe BatchApi::RackMiddleware do
4
+ describe "#initialize" do
5
+ it "allows access to the BatchApi configuration" do
6
+ limit = rand * 100
7
+ middleware = BatchApi::RackMiddleware.new(double("app")) do |conf|
8
+ conf.limit = limit
9
+ end
10
+ expect(BatchApi.config.limit).to eq(limit)
11
+ end
12
+ end
13
+
14
+ describe "#call" do
15
+ let(:endpoint) { "/foo/bar" }
16
+ let(:verb) { "run" }
17
+ let(:app) { double("app") }
18
+
19
+ let(:middleware) {
20
+ BatchApi::RackMiddleware.new(app) do |conf|
21
+ conf.endpoint = endpoint
22
+ conf.verb = verb
23
+ end
24
+ }
25
+
26
+ context "if it's a batch call" do
27
+ let(:env) { {
28
+ "PATH_INFO" => endpoint,
29
+ "REQUEST_METHOD" => verb.upcase,
30
+ # other stuff
31
+ "CONTENT_TYPE"=>"application/x-www-form-urlencoded",
32
+ "GATEWAY_INTERFACE"=>"CGI/1.1",
33
+ "QUERY_STRING"=>"",
34
+ "REMOTE_ADDR"=>"127.0.0.1",
35
+ "REMOTE_HOST"=>"1035.spotilocal.com",
36
+ "REQUEST_URI"=>"http://localhost:3000/batch",
37
+ "SCRIPT_NAME"=>"",
38
+ "rack.input" => StringIO.new,
39
+ "rack.errors" => StringIO.new,
40
+ "SERVER_NAME"=>"localhost",
41
+ "SERVER_PORT"=>"3000",
42
+ "SERVER_PROTOCOL"=>"HTTP/1.1",
43
+ "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.3/2012-02-16)",
44
+ "HTTP_USER_AGENT"=>"curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5",
45
+ "HTTP_HOST"=>"localhost:3000"
46
+ } }
47
+
48
+ let(:request) { Rack::Request.new(env) }
49
+ let(:result) { {a: 2, b: {c: 3}} }
50
+ let(:processor) { double("processor", :execute! => result) }
51
+
52
+ before :each do
53
+ allow(BatchApi::Processor).to receive(:new).and_return(processor)
54
+ end
55
+
56
+ it "processes the batch request" do
57
+ allow(Rack::Request).to receive(:new).with(env).and_return(request)
58
+ expect(BatchApi::Processor).to receive(:new).with(request, app).and_return(processor)
59
+ middleware.call(env)
60
+ end
61
+
62
+ context "for a successful set of calls" do
63
+ it "returns the JSON-encoded result as the body" do
64
+ output = middleware.call(env)
65
+ expect(output[2]).to eq([MultiJson.dump(result)])
66
+ end
67
+
68
+ it "returns a 200" do
69
+ expect(middleware.call(env)[0]).to eq(200)
70
+ end
71
+
72
+ it "sets the content type" do
73
+ expect(middleware.call(env)[1]).to include("Content-Type" => "application/json")
74
+ end
75
+ end
76
+
77
+ context "for BatchApi errors" do
78
+ it "returns a rendered ErrorWrapper" do
79
+ err, result = StandardError.new, double
80
+ error = double("error object", render: result)
81
+ allow(BatchApi::Processor).to receive(:new).and_raise(err)
82
+ expect(BatchApi::ErrorWrapper).to receive(:new).with(err).and_return(
83
+ error
84
+ )
85
+ expect(middleware.call(env)).to eq(result)
86
+ end
87
+ end
88
+ end
89
+
90
+ context "if it's not a batch request" do
91
+ let(:env) { {
92
+ "PATH_INFO" => "/not/batch",
93
+ "REQUEST_METHOD" => verb.upcase
94
+ } }
95
+
96
+ it "just calls the app onward and returns the result" do
97
+ output = double("output")
98
+ expect(app).to receive(:call).with(env).and_return(output)
99
+ middleware.call(env)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ require 'batch_api/response'
3
+
4
+ describe BatchApi::Response do
5
+
6
+ let(:raw_response) { [200, {}, ["ab", "cd", "ef"]] }
7
+ let(:response) { BatchApi::Response.new(raw_response) }
8
+
9
+ [:status, :body, :headers].each do |attr|
10
+ local_attr = attr
11
+ it "has an accessor for #{local_attr}" do
12
+ expect(response).to respond_to(local_attr)
13
+ end
14
+ end
15
+
16
+ describe "#initialize" do
17
+ it "sets status to the HTTP status code" do
18
+ expect(response.status).to eq(raw_response.first)
19
+ end
20
+
21
+ it "sets body to the HTTP body turned into a string" do
22
+ expect(response.body).to eq(raw_response[2].join)
23
+ end
24
+
25
+ it "sets headers to the HTTP headers" do
26
+ expect(response.headers).to eq(raw_response[1])
27
+ end
28
+ end
29
+
30
+ describe "#as_json" do
31
+ it "creates the expected hash" do
32
+ expect(response.as_json).to eq({
33
+ body: response.body,
34
+ status: response.status,
35
+ headers: response.headers
36
+ })
37
+ end
38
+
39
+ it "accepts options" do
40
+ expect(response.as_json(foo: :bar)).not_to be_nil
41
+ end
42
+
43
+ it "leaves out items that are blank" do
44
+ response.status = response.body = nil
45
+ expect(response.as_json).to eq({headers: raw_response[1]})
46
+ end
47
+
48
+ it "includes items that are false" do
49
+ response.body = false
50
+ expect(response.as_json[:body]).to eq(false)
51
+ end
52
+ end
53
+ end