restforce 2.5.4 → 4.0.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 (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