restforce 2.5.4 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +56 -0
  3. data/.rubocop.yml +27 -14
  4. data/.rubocop_todo.yml +128 -81
  5. data/CHANGELOG.md +37 -3
  6. data/CONTRIBUTING.md +3 -3
  7. data/Gemfile +4 -2
  8. data/Guardfile +3 -1
  9. data/LICENSE +1 -1
  10. data/README.md +120 -19
  11. data/Rakefile +2 -1
  12. data/lib/restforce.rb +23 -1
  13. data/lib/restforce/abstract_client.rb +3 -0
  14. data/lib/restforce/attachment.rb +3 -0
  15. data/lib/restforce/client.rb +2 -0
  16. data/lib/restforce/collection.rb +3 -1
  17. data/lib/restforce/concerns/api.rb +20 -14
  18. data/lib/restforce/concerns/authentication.rb +2 -0
  19. data/lib/restforce/concerns/base.rb +2 -0
  20. data/lib/restforce/concerns/batch_api.rb +87 -0
  21. data/lib/restforce/concerns/caching.rb +4 -2
  22. data/lib/restforce/concerns/canvas.rb +3 -0
  23. data/lib/restforce/concerns/connection.rb +26 -20
  24. data/lib/restforce/concerns/picklists.rb +9 -6
  25. data/lib/restforce/concerns/streaming.rb +60 -1
  26. data/lib/restforce/concerns/verbs.rb +3 -1
  27. data/lib/restforce/config.rb +4 -1
  28. data/lib/restforce/data/client.rb +2 -0
  29. data/lib/restforce/document.rb +3 -0
  30. data/lib/restforce/mash.rb +2 -0
  31. data/lib/restforce/middleware.rb +2 -0
  32. data/lib/restforce/middleware/authentication.rb +8 -6
  33. data/lib/restforce/middleware/authentication/password.rb +2 -0
  34. data/lib/restforce/middleware/authentication/token.rb +2 -0
  35. data/lib/restforce/middleware/authorization.rb +3 -1
  36. data/lib/restforce/middleware/caching.rb +3 -1
  37. data/lib/restforce/middleware/custom_headers.rb +2 -0
  38. data/lib/restforce/middleware/gzip.rb +5 -3
  39. data/lib/restforce/middleware/instance_url.rb +7 -3
  40. data/lib/restforce/middleware/logger.rb +2 -0
  41. data/lib/restforce/middleware/mashify.rb +2 -0
  42. data/lib/restforce/middleware/multipart.rb +8 -4
  43. data/lib/restforce/middleware/raise_error.rb +26 -8
  44. data/lib/restforce/patches/parts.rb +2 -0
  45. data/lib/restforce/signed_request.rb +3 -0
  46. data/lib/restforce/sobject.rb +3 -0
  47. data/lib/restforce/tooling/client.rb +5 -3
  48. data/lib/restforce/upload_io.rb +2 -0
  49. data/lib/restforce/version.rb +3 -1
  50. data/restforce.gemspec +19 -12
  51. data/spec/fixtures/sobject/sobject_describe_success_response.json +48 -1
  52. data/spec/integration/abstract_client_spec.rb +51 -7
  53. data/spec/integration/data/client_spec.rb +24 -5
  54. data/spec/spec_helper.rb +2 -0
  55. data/spec/support/client_integration.rb +2 -0
  56. data/spec/support/concerns.rb +2 -0
  57. data/spec/support/event_machine.rb +2 -0
  58. data/spec/support/fixture_helpers.rb +4 -2
  59. data/spec/support/matchers.rb +2 -0
  60. data/spec/support/middleware.rb +3 -1
  61. data/spec/support/mock_cache.rb +4 -2
  62. data/spec/unit/abstract_client_spec.rb +2 -0
  63. data/spec/unit/attachment_spec.rb +2 -0
  64. data/spec/unit/collection_spec.rb +5 -3
  65. data/spec/unit/concerns/api_spec.rb +40 -11
  66. data/spec/unit/concerns/authentication_spec.rb +4 -2
  67. data/spec/unit/concerns/base_spec.rb +2 -0
  68. data/spec/unit/concerns/batch_api_spec.rb +107 -0
  69. data/spec/unit/concerns/caching_spec.rb +2 -0
  70. data/spec/unit/concerns/canvas_spec.rb +3 -1
  71. data/spec/unit/concerns/connection_spec.rb +5 -3
  72. data/spec/unit/concerns/streaming_spec.rb +115 -1
  73. data/spec/unit/config_spec.rb +10 -8
  74. data/spec/unit/data/client_spec.rb +2 -0
  75. data/spec/unit/document_spec.rb +2 -0
  76. data/spec/unit/mash_spec.rb +3 -1
  77. data/spec/unit/middleware/authentication/password_spec.rb +2 -0
  78. data/spec/unit/middleware/authentication/token_spec.rb +2 -0
  79. data/spec/unit/middleware/authentication_spec.rb +3 -1
  80. data/spec/unit/middleware/authorization_spec.rb +2 -0
  81. data/spec/unit/middleware/custom_headers_spec.rb +3 -1
  82. data/spec/unit/middleware/gzip_spec.rb +4 -2
  83. data/spec/unit/middleware/instance_url_spec.rb +2 -0
  84. data/spec/unit/middleware/logger_spec.rb +2 -0
  85. data/spec/unit/middleware/mashify_spec.rb +3 -1
  86. data/spec/unit/middleware/raise_error_spec.rb +34 -11
  87. data/spec/unit/signed_request_spec.rb +2 -0
  88. data/spec/unit/sobject_spec.rb +5 -3
  89. data/spec/unit/tooling/client_spec.rb +2 -0
  90. metadata +38 -20
  91. data/.travis.yml +0 -16
  92. data/Gemfile.travis +0 -8
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Concerns::Caching do
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Concerns::Canvas do
4
- let(:options) { Hash.new }
6
+ let(:options) { {} }
5
7
 
6
8
  before do
7
9
  client.stub options: options
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Concerns::Connection do
@@ -34,7 +36,7 @@ describe Restforce::Concerns::Connection do
34
36
  describe 'with mashify not specified' do
35
37
  it 'includes the Mashify middleware' do
36
38
  client.middleware.handlers.index(Restforce::Middleware::Mashify).
37
- should_not be_nil
39
+ should_not be_nil
38
40
  end
39
41
  end
40
42
 
@@ -45,7 +47,7 @@ describe Restforce::Concerns::Connection do
45
47
 
46
48
  it 'includes the Mashify middleware' do
47
49
  client.middleware.handlers.index(Restforce::Middleware::Mashify).
48
- should_not be_nil
50
+ should_not be_nil
49
51
  end
50
52
  end
51
53
 
@@ -56,7 +58,7 @@ describe Restforce::Concerns::Connection do
56
58
 
57
59
  it 'does not include the Mashify middleware' do
58
60
  client.middleware.handlers.index(Restforce::Middleware::Mashify).
59
- should be_nil
61
+ should be_nil
60
62
  end
61
63
  end
62
64
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Concerns::Streaming, event_machine: true do
4
6
  describe '.subscribe' do
5
- let(:channels) { %w( channel1 channel2 ) }
7
+ let(:channels) { %w[channel1 channel2] }
6
8
  let(:topics) { channels.map { |c| "/topic/#{c}" } }
7
9
  let(:subscribe_block) { lambda { 'subscribe' } }
8
10
  let(:faye_double) { double('Faye') }
@@ -15,6 +17,33 @@ describe Restforce::Concerns::Streaming, event_machine: true do
15
17
 
16
18
  client.subscribe(channels, &subscribe_block)
17
19
  end
20
+
21
+ context "replay_handlers" do
22
+ before {
23
+ faye_double.should_receive(:subscribe).at_least(1)
24
+ client.stub faye: faye_double
25
+ }
26
+
27
+ it 'registers nil handlers when no replay option is given' do
28
+ client.subscribe(channels, &subscribe_block)
29
+ client.replay_handlers.should eq('channel1' => nil, 'channel2' => nil)
30
+ end
31
+
32
+ it 'registers a replay_handler for each channel given' do
33
+ client.subscribe(channels, replay: -2, &subscribe_block)
34
+ client.replay_handlers.should eq('channel1' => -2, 'channel2' => -2)
35
+ end
36
+
37
+ it 'replaces earlier handlers in subsequent calls' do
38
+ client.subscribe(%w[channel1 channel2], replay: 2, &subscribe_block)
39
+ client.subscribe(%w[channel2 channel3], replay: 3, &subscribe_block)
40
+ client.replay_handlers.should eq(
41
+ 'channel1' => 2,
42
+ 'channel2' => 3,
43
+ 'channel3' => 3
44
+ )
45
+ end
46
+ end
18
47
  end
19
48
 
20
49
  describe '.faye' do
@@ -40,6 +69,8 @@ describe Restforce::Concerns::Streaming, event_machine: true do
40
69
  faye_double.should_receive(:set_header).with('Authorization', 'OAuth secret2')
41
70
  faye_double.should_receive(:bind).with('transport:down').and_yield
42
71
  faye_double.should_receive(:bind).with('transport:up').and_yield
72
+ faye_double.should_receive(:add_extension).with \
73
+ kind_of(Restforce::Concerns::Streaming::ReplayExtension)
43
74
  subject
44
75
  end
45
76
  end
@@ -50,4 +81,87 @@ describe Restforce::Concerns::Streaming, event_machine: true do
50
81
  end
51
82
  end
52
83
  end
84
+
85
+ describe Restforce::Concerns::Streaming::ReplayExtension do
86
+ let(:handlers) { {} }
87
+ let(:extension) { Restforce::Concerns::Streaming::ReplayExtension.new(handlers) }
88
+
89
+ it 'sends nil without a specified handler' do
90
+ output = subscribe(extension, to: "channel1")
91
+ read_replay(output).should eq('/topic/channel1' => nil)
92
+ end
93
+
94
+ it 'with a scalar replay id' do
95
+ handlers['channel1'] = -2
96
+ output = subscribe(extension, to: "channel1")
97
+ read_replay(output).should eq('/topic/channel1' => -2)
98
+ end
99
+
100
+ it 'with a hash' do
101
+ hash_handler = { 'channel1' => -1, 'channel2' => -2 }
102
+
103
+ handlers['channel1'] = hash_handler
104
+ handlers['channel2'] = hash_handler
105
+
106
+ output = subscribe(extension, to: "channel1")
107
+ read_replay(output).should eq('/topic/channel1' => -1)
108
+
109
+ output = subscribe(extension, to: "channel2")
110
+ read_replay(output).should eq('/topic/channel2' => -2)
111
+ end
112
+
113
+ it 'with an object' do
114
+ custom_handler = double('custom_handler')
115
+ custom_handler.should_receive(:[]).and_return(123)
116
+ handlers['channel1'] = custom_handler
117
+
118
+ output = subscribe(extension, to: "channel1")
119
+ read_replay(output).should eq('/topic/channel1' => 123)
120
+ end
121
+
122
+ it 'remembers the last replayId' do
123
+ handler = { 'channel1' => 41 }
124
+ handlers['channel1'] = handler
125
+ message = {
126
+ 'channel' => '/topic/channel1',
127
+ 'data' => {
128
+ 'event' => { 'replayId' => 42 }
129
+ }
130
+ }
131
+
132
+ extension.incoming(message, ->(m) {})
133
+ handler.should eq('channel1' => 42)
134
+ end
135
+
136
+ it 'when an incoming message has no replayId' do
137
+ handler = { 'channel1' => 41 }
138
+ handlers['channel1'] = handler
139
+
140
+ message = {
141
+ 'channel' => '/topic/channel1',
142
+ 'data' => {}
143
+ }
144
+
145
+ extension.incoming(message, ->(m) {})
146
+ handler.should eq('channel1' => 41)
147
+ end
148
+
149
+ private
150
+
151
+ def subscribe(extension, options = {})
152
+ output = nil
153
+ message = {
154
+ 'channel' => '/meta/subscribe',
155
+ 'subscription' => "/topic/#{options[:to]}"
156
+ }
157
+ extension.outgoing(message, ->(m) {
158
+ output = m
159
+ })
160
+ output
161
+ end
162
+
163
+ def read_replay(message)
164
+ message.fetch('ext', {})['replay']
165
+ end
166
+ end
53
167
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce do
@@ -25,9 +27,9 @@ describe Restforce do
25
27
  its(:authentication_retries) { should eq 3 }
26
28
  its(:adapter) { should eq Faraday.default_adapter }
27
29
  its(:ssl) { should eq({}) }
28
- [:username, :password, :security_token, :client_id, :client_secret,
29
- :oauth_token, :refresh_token, :instance_url, :compress, :timeout,
30
- :proxy_uri, :authentication_callback, :mashify, :request_headers].each do |attr|
30
+ %i[username password security_token client_id client_secret
31
+ oauth_token refresh_token instance_url compress timeout
32
+ proxy_uri authentication_callback mashify request_headers].each do |attr|
31
33
  its(attr) { should be_nil }
32
34
  end
33
35
  end
@@ -42,7 +44,7 @@ describe Restforce do
42
44
  'SALESFORCE_PROXY_URI' => 'proxy',
43
45
  'SALESFORCE_HOST' => 'test.host.com',
44
46
  'SALESFORCE_API_VERSION' => '37.0' }.
45
- each { |var, value| ENV.stub(:[]).with(var).and_return(value) }
47
+ each { |var, value| ENV.stub(:[]).with(var).and_return(value) }
46
48
  end
47
49
 
48
50
  its(:username) { should eq 'foo' }
@@ -57,10 +59,10 @@ describe Restforce do
57
59
  end
58
60
 
59
61
  describe '#configure' do
60
- [:username, :password, :security_token, :client_id, :client_secret, :compress,
61
- :timeout, :oauth_token, :refresh_token, :instance_url, :api_version, :host, :mashify,
62
- :authentication_retries, :proxy_uri, :authentication_callback, :ssl,
63
- :request_headers, :log_level, :logger].each do |attr|
62
+ %i[username password security_token client_id client_secret compress
63
+ timeout oauth_token refresh_token instance_url api_version host mashify
64
+ authentication_retries proxy_uri authentication_callback ssl
65
+ request_headers log_level logger].each do |attr|
64
66
  it "allows #{attr} to be set" do
65
67
  Restforce.configure do |config|
66
68
  config.send("#{attr}=", 'foobar')
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Client do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Document do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Mash do
@@ -6,7 +8,7 @@ describe Restforce::Mash do
6
8
 
7
9
  context 'when array' do
8
10
  let(:input) { [{ foo: 'hello' }, { bar: 'world' }] }
9
- it { should be_all { |obj| expect(obj).to be_a Restforce::Mash } }
11
+ it { should(be_all { |obj| expect(obj).to be_a Restforce::Mash }) }
10
12
  end
11
13
  end
12
14
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::Authentication::Password do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::Authentication::Token do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::Authentication do
@@ -58,7 +60,7 @@ describe Restforce::Middleware::Authentication do
58
60
  should include FaradayMiddleware::ParseJson,
59
61
  Faraday::Adapter::NetHttp
60
62
  }
61
- its(:handlers) { should_not include Restforce::Middleware::Logger }
63
+ its(:handlers) { should_not include Restforce::Middleware::Logger }
62
64
  end
63
65
 
64
66
  context 'with logging enabled' do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::Authorization do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::CustomHeaders do
@@ -13,7 +15,7 @@ describe Restforce::Middleware::CustomHeaders do
13
15
  context 'when :request_headers are not a Hash' do
14
16
  let(:options) { { request_headers: 'bad header' } }
15
17
 
16
- it { should_not change { env[:request_headers] } }
18
+ it { should_not(change { env[:request_headers] }) }
17
19
  end
18
20
  end
19
21
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::Gzip do
@@ -29,7 +31,7 @@ describe Restforce::Middleware::Gzip do
29
31
  end
30
32
 
31
33
  context 'when :compress is false' do
32
- it { should_not change { env[:request_headers]['Accept-Encoding'] } }
34
+ it { should_not(change { env[:request_headers]['Accept-Encoding'] }) }
33
35
  end
34
36
 
35
37
  context 'when :compress is true' do
@@ -37,7 +39,7 @@ describe Restforce::Middleware::Gzip do
37
39
  options[:compress] = true
38
40
  end
39
41
 
40
- it { should change { env[:request_headers]['Accept-Encoding'] }.to('gzip') }
42
+ it { should(change { env[:request_headers]['Accept-Encoding'] }.to('gzip')) }
41
43
  end
42
44
  end
43
45
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::InstanceURL do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::Logger do
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::Mashify do
4
6
  let(:env) { { body: JSON.parse(fixture('sobject/query_success_response')) } }
5
7
  subject(:middleware) {
6
- described_class.new(lambda {|env|
8
+ described_class.new(lambda { |env|
7
9
  Faraday::Response.new(env)
8
10
  }, client, options)
9
11
  }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Middleware::RaiseError do
@@ -11,34 +13,46 @@ describe Restforce::Middleware::RaiseError do
11
13
  context 'when the status code is 404' do
12
14
  let(:status) { 404 }
13
15
 
14
- it "raises an error" do
15
- expect { on_complete }.to raise_error Faraday::Error::ResourceNotFound,
16
+ it 'raises Restforce::NotFoundError' do
17
+ expect { on_complete }.to raise_error Restforce::NotFoundError,
16
18
  'INVALID_FIELD: error_message'
17
19
  end
20
+
21
+ it 'raises an error that inherits from Faraday::Error::ResourceNotFound' do
22
+ expect { on_complete }.to raise_error Faraday::Error::ResourceNotFound
23
+ end
18
24
  end
19
25
 
20
26
  context 'when the status code is 300' do
21
27
  let(:status) { 300 }
22
28
 
23
- it "raises an error" do
24
- expect { on_complete }.to raise_error Faraday::Error::ClientError,
29
+ it 'raises Restforce::MatchesMultipleError' do
30
+ expect { on_complete }.to raise_error Restforce::MatchesMultipleError,
25
31
  /300: The external ID provided/
26
32
  end
33
+
34
+ it 'raises an error that inherits from Faraday::Error::ClientError' do
35
+ expect { on_complete }.to raise_error Faraday::Error::ClientError
36
+ end
27
37
  end
28
38
 
29
39
  context 'when the status code is 400' do
30
40
  let(:status) { 400 }
31
41
 
32
- it "raises an error" do
33
- expect { on_complete }.to raise_error Faraday::Error::ClientError,
42
+ it "raises an error derived from the response's errorCode" do
43
+ expect { on_complete }.to raise_error Restforce::ErrorCode::InvalidField,
34
44
  'INVALID_FIELD: error_message'
35
45
  end
46
+
47
+ it 'raises an error that inherits from Faraday::Error::ClientError' do
48
+ expect { on_complete }.to raise_error Faraday::Error::ClientError
49
+ end
36
50
  end
37
51
 
38
52
  context 'when the status code is 401' do
39
53
  let(:status) { 401 }
40
54
 
41
- it "raises an error" do
55
+ it 'raises Restforce::UnauthorizedError' do
42
56
  expect { on_complete }.to raise_error Restforce::UnauthorizedError,
43
57
  'INVALID_FIELD: error_message'
44
58
  end
@@ -47,17 +61,26 @@ describe Restforce::Middleware::RaiseError do
47
61
  context 'when the status code is 413' do
48
62
  let(:status) { 413 }
49
63
 
50
- it "raises an error" do
51
- expect { on_complete }.to raise_error Faraday::Error::ClientError,
64
+ it 'raises Restforce::EntityTooLargeError' do
65
+ expect { on_complete }.to raise_error Restforce::EntityTooLargeError,
52
66
  '413: Request Entity Too Large'
53
67
  end
68
+
69
+ it 'raises an error that inherits from Faraday::Error::ClientError' do
70
+ expect { on_complete }.to raise_error Faraday::Error::ClientError
71
+ end
54
72
  end
55
73
 
56
74
  context 'when status is 400+ and body is a string' do
57
75
  let(:body) { 'An error occured' }
58
- let(:status) { 404 }
76
+ let(:status) { 400 }
77
+
78
+ it 'raises a generic Restforce::ResponseError' do
79
+ expect { on_complete }.to raise_error Restforce::ResponseError,
80
+ "(error code missing): #{body}"
81
+ end
59
82
 
60
- it 'raises an error with a non-existing error code' do
83
+ it 'raises an error that inherits from Faraday::Error::ClientError' do
61
84
  expect { on_complete }.to raise_error Faraday::Error::ClientError,
62
85
  "(error code missing): #{body}"
63
86
  end