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
  shared_examples_for Restforce::Data::Client do
@@ -8,15 +10,20 @@ shared_examples_for Restforce::Data::Client do
8
10
  context 'when given a picklist field' do
9
11
  subject { client.picklist_values('Account', 'Picklist_Field') }
10
12
  it { should be_an Array }
11
- its(:length) { should eq 3 }
12
- it { should include_picklist_values %w(one two three) }
13
+ its(:length) { should eq 10 }
14
+ it {
15
+ should include_picklist_values %w[
16
+ one two three control_four control_five
17
+ control_six control_seven control_eight control_nine control_ten
18
+ ]
19
+ }
13
20
  end
14
21
 
15
22
  context 'when given a multipicklist field' do
16
23
  subject { client.picklist_values('Account', 'Picklist_Multiselect_Field') }
17
24
  it { should be_an Array }
18
25
  its(:length) { should eq 3 }
19
- it { should include_picklist_values %w(four five six) }
26
+ it { should include_picklist_values %w[four five six] }
20
27
  end
21
28
 
22
29
  describe 'dependent picklists' do
@@ -29,10 +36,22 @@ shared_examples_for Restforce::Data::Client do
29
36
 
30
37
  it { should be_an Array }
31
38
  its(:length) { should eq 2 }
32
- it { should include_picklist_values %w(seven eight) }
39
+ it { should include_picklist_values %w[seven eight] }
33
40
  it { should_not include_picklist_values ['nine'] }
34
41
  end
35
42
 
43
+ context 'when given a picklist field that has a dependency index greater than 8' do
44
+ subject do
45
+ client.picklist_values('Account',
46
+ 'Dependent_Picklist_Field',
47
+ valid_for: 'control_ten')
48
+ end
49
+
50
+ it { should be_an Array }
51
+ its(:length) { should eq 1 }
52
+ it { should include_picklist_values %w[ten] }
53
+ end
54
+
36
55
  context 'when given a picklist field that does not have a dependency' do
37
56
  subject do
38
57
  client.picklist_values('Account', 'Picklist_Field', valid_for: 'one')
@@ -90,7 +109,7 @@ shared_examples_for Restforce::Data::Client do
90
109
  it 'subscribes to each pushtopic' do
91
110
  client.faye.should_receive(:subscribe).with(['/topic/PushTopic1',
92
111
  '/topic/PushTopic2'])
93
- client.subscribe(%w(PushTopic1 PushTopic2))
112
+ client.subscribe(%w[PushTopic1 PushTopic2])
94
113
  end
95
114
  end
96
115
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'simplecov'
2
4
  SimpleCov.start
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ClientIntegrationExampleGroup
2
4
  def self.included(base)
3
5
  base.class_eval do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ConcernsExampleGroup
2
4
  def self.included(base)
3
5
  base.class_eval do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec.configure do |config|
2
4
  config.before do
3
5
  EventMachine.stub(:connect) if defined?(EventMachine)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FixtureHelpers
2
4
  module InstanceMethods
3
5
  def stub_api_request(endpoint, options = {})
@@ -25,8 +27,8 @@ module FixtureHelpers
25
27
  stub
26
28
  end
27
29
 
28
- def fixture(f)
29
- File.read(File.expand_path("../../fixtures/#{f}.json", __FILE__))
30
+ def fixture(filename)
31
+ File.read(File.expand_path("../../fixtures/#{filename}.json", __FILE__))
30
32
  end
31
33
  end
32
34
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RSpec::Matchers.define :include_picklist_values do |expected|
2
4
  match do |actual|
3
5
  actual.all? { |picklist_value| expected.include? picklist_value['value'] }
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MiddlewareExampleGroup
2
4
  def self.included(base)
3
5
  base.class_eval do
4
- let(:app) { double('@app', call: nil) }
6
+ let(:app) { double('@app', call: nil) }
5
7
  let(:env) { { request_headers: {}, response_headers: {} } }
6
8
  let(:retries) { 3 }
7
9
  let(:options) { {} }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class MockCache
2
4
  def initialize
3
5
  @storage = {}
@@ -11,8 +13,8 @@ class MockCache
11
13
  @storage[key] = value
12
14
  end
13
15
 
14
- def fetch(key, &block)
15
- @storage[key] ||= block.call
16
+ def fetch(key)
17
+ @storage[key] ||= yield
16
18
  end
17
19
 
18
20
  def delete(key)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::AbstractClient do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Attachment do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Collection do
@@ -16,7 +18,7 @@ describe Restforce::Collection do
16
18
  its(:page_size) { should eq 1 }
17
19
 
18
20
  describe 'each record' do
19
- it { should be_all { |record| expect(record).to be_a Restforce::SObject } }
21
+ it { should(be_all { |record| expect(record).to be_a Restforce::SObject }) }
20
22
  end
21
23
  end
22
24
 
@@ -51,11 +53,11 @@ describe Restforce::Collection do
51
53
  end
52
54
 
53
55
  its(:pages) do
54
- should be_all { |page| expect(page).to be_a Restforce::Collection }
56
+ should(be_all { |page| expect(page).to be_a Restforce::Collection })
55
57
  end
56
58
 
57
59
  its(:has_next_page?) { should be_true }
58
- it { should be_all { |record| expect(record).to be_a Restforce::SObject } }
60
+ it { should(be_all { |record| expect(record).to be_a Restforce::SObject }) }
59
61
  its(:next_page) { should be_a Restforce::Collection }
60
62
  end
61
63
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Concerns::API do
@@ -27,7 +29,7 @@ describe Restforce::Concerns::API do
27
29
  it 'returns the body' do
28
30
  start_string = '2002-10-31T00:02:02Z'
29
31
  end_string = '2003-10-31T00:02:02Z'
30
- url = "/sobjects/Whizbang/updated/?start=#{start_string}&end=#{end_string}"
32
+ url = "sobjects/Whizbang/updated/?start=#{start_string}&end=#{end_string}"
31
33
  client.should_receive(:api_get).
32
34
  with(url).
33
35
  and_return(response)
@@ -43,7 +45,7 @@ describe Restforce::Concerns::API do
43
45
  it 'returns the body' do
44
46
  start_string = '2002-10-31T00:02:02Z'
45
47
  end_string = '2003-10-31T00:02:02Z'
46
- url = "/sobjects/Whizbang/deleted/?start=#{start_string}&end=#{end_string}"
48
+ url = "sobjects/Whizbang/deleted/?start=#{start_string}&end=#{end_string}"
47
49
  client.should_receive(:api_get).
48
50
  with(url).
49
51
  and_return(response)
@@ -259,7 +261,7 @@ describe Restforce::Concerns::API do
259
261
  end
260
262
  end
261
263
 
262
- [:create, :update, :upsert, :destroy].each do |method|
264
+ %i[create update upsert destroy].each do |method|
263
265
  describe ".#{method}" do
264
266
  let(:args) { [] }
265
267
  subject(:result) { client.send(method, *args) }
@@ -289,7 +291,7 @@ describe Restforce::Concerns::API do
289
291
 
290
292
  describe '.create!' do
291
293
  let(:sobject) { 'Whizbang' }
292
- let(:attrs) { Hash.new }
294
+ let(:attrs) { {} }
293
295
  subject(:result) { client.create!(sobject, attrs) }
294
296
 
295
297
  it 'send an HTTP POST, and returns the id of the record' do
@@ -303,7 +305,7 @@ describe Restforce::Concerns::API do
303
305
 
304
306
  describe '.update!' do
305
307
  let(:sobject) { 'Whizbang' }
306
- let(:attrs) { Hash.new }
308
+ let(:attrs) { {} }
307
309
  subject(:result) { client.update!(sobject, attrs) }
308
310
 
309
311
  context 'when the id field is present' do
@@ -342,17 +344,27 @@ describe Restforce::Concerns::API do
342
344
 
343
345
  context 'when the record is found and updated' do
344
346
  it 'returns true' do
345
- response.body.stub :[]
347
+ response.stub(:body) { {} }
346
348
  client.should_receive(:api_patch).
347
349
  with('sobjects/Whizbang/External_ID__c/1234', {}).
348
350
  and_return(response)
349
351
  expect(result).to be_true
350
352
  end
353
+
354
+ context 'and the response body is a string' do
355
+ it 'returns true' do
356
+ response.stub(:body) { '' }
357
+ client.should_receive(:api_patch).
358
+ with('sobjects/Whizbang/External_ID__c/1234', {}).
359
+ and_return(response)
360
+ expect(result).to be_true
361
+ end
362
+ end
351
363
  end
352
364
 
353
365
  context 'when the record is found and created' do
354
366
  it 'returns the id of the record' do
355
- response.body.stub(:[]).with('id').and_return('4321')
367
+ response.stub(:body) { { "id" => "4321" } }
356
368
  client.should_receive(:api_patch).
357
369
  with('sobjects/Whizbang/External_ID__c/1234', {}).
358
370
  and_return(response)
@@ -361,7 +373,7 @@ describe Restforce::Concerns::API do
361
373
  end
362
374
 
363
375
  context 'when the external id field is missing from the attrs' do
364
- let(:attrs) { Hash.new }
376
+ let(:attrs) { {} }
365
377
 
366
378
  it 'raises an argument error' do
367
379
  expect { client.upsert!(sobject, field, attrs) }.
@@ -376,7 +388,7 @@ describe Restforce::Concerns::API do
376
388
 
377
389
  context 'and the value for Id is provided' do
378
390
  it 'returns the id of the record, and original record still contains id' do
379
- response.body.stub(:[]).with('id').and_return('4321')
391
+ response.stub(:body) { { "id" => "4321" } }
380
392
  client.should_receive(:api_patch).
381
393
  with('sobjects/Whizbang/Id/4321', {}).
382
394
  and_return(response)
@@ -389,7 +401,7 @@ describe Restforce::Concerns::API do
389
401
  let(:attrs) { { 'External_ID__c' => '1234' } }
390
402
 
391
403
  it 'uses POST to create the record' do
392
- response.body.stub(:[]).with('id').and_return('4321')
404
+ response.stub(:body) { { "id" => "4321" } }
393
405
  client.should_receive(:options).and_return(api_version: 38.0)
394
406
  client.should_receive(:api_post).
395
407
  with('sobjects/Whizbang/Id', attrs).
@@ -415,7 +427,7 @@ describe Restforce::Concerns::API do
415
427
 
416
428
  context 'when the record is found and updated' do
417
429
  it 'returns true' do
418
- response.body.stub :[]
430
+ response.stub(:body) { {} }
419
431
  client.should_receive(:api_patch).
420
432
  with('sobjects/Whizbang/External_ID__c/%E3%81%82', {}).
421
433
  and_return(response)
@@ -423,6 +435,23 @@ describe Restforce::Concerns::API do
423
435
  end
424
436
  end
425
437
  end
438
+
439
+ describe '.upsert! with Fixnum argument' do
440
+ let(:sobject) { 'Whizbang' }
441
+ let(:field) { :External_ID__c }
442
+ let(:attrs) { { 'External_ID__c' => 1234 } }
443
+ subject(:result) { client.upsert!(sobject, field, attrs) }
444
+
445
+ context 'when the record is found and updated' do
446
+ it 'returns true' do
447
+ response.stub(:body) { {} }
448
+ client.should_receive(:api_patch).
449
+ with('sobjects/Whizbang/External_ID__c/1234', {}).
450
+ and_return(response)
451
+ expect(result).to be_true
452
+ end
453
+ end
454
+ end
426
455
  end
427
456
 
428
457
  describe '.destroy!' do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Restforce::Concerns::Authentication do
@@ -54,7 +56,7 @@ describe Restforce::Concerns::Authentication do
54
56
 
55
57
  describe '.username_password?' do
56
58
  subject { client.username_password? }
57
- let(:options) { Hash.new }
59
+ let(:options) { {} }
58
60
 
59
61
  before do
60
62
  client.stub options: options
@@ -78,7 +80,7 @@ describe Restforce::Concerns::Authentication do
78
80
 
79
81
  describe '.oauth_refresh?' do
80
82
  subject { client.oauth_refresh? }
81
- let(:options) { Hash.new }
83
+ let(:options) { {} }
82
84
 
83
85
  before do
84
86
  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::Base do
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restforce::Concerns::BatchAPI do
6
+ let(:endpoint) { 'composite/batch' }
7
+
8
+ before do
9
+ client.should_receive(:options).and_return(api_version: 34.0)
10
+ end
11
+
12
+ shared_examples_for 'batched requests' do
13
+ it '#create' do
14
+ client.
15
+ should_receive(:api_post).
16
+ with(endpoint, { batchRequests: [
17
+ { method: 'POST', url: 'v34.0/sobjects/Object', richInput: { name: 'test' } }
18
+ ], haltOnError: halt_on_error }.to_json).
19
+ and_return(response)
20
+
21
+ client.send(method) do |subrequests|
22
+ subrequests.create('Object', name: 'test')
23
+ end
24
+ end
25
+
26
+ it '#update' do
27
+ client.
28
+ should_receive(:api_post).
29
+ with(endpoint, { batchRequests: [
30
+ { method: 'PATCH', url: "v34.0/sobjects/Object/123", richInput: {
31
+ name: 'test'
32
+ } }
33
+ ], haltOnError: halt_on_error }.to_json).
34
+ and_return(response)
35
+
36
+ client.send(method) do |subrequests|
37
+ subrequests.update('Object', id: '123', name: 'test')
38
+ end
39
+ end
40
+
41
+ it '#destroy' do
42
+ client.
43
+ should_receive(:api_post).
44
+ with(endpoint, { batchRequests: [
45
+ { method: 'DELETE', url: "v34.0/sobjects/Object/123" }
46
+ ], haltOnError: halt_on_error }.to_json).
47
+ and_return(response)
48
+
49
+ client.send(method) do |subrequests|
50
+ subrequests.destroy('Object', '123')
51
+ end
52
+ end
53
+
54
+ it '#upsert' do
55
+ client.
56
+ should_receive(:api_post).
57
+ with(endpoint, { batchRequests: [
58
+ { method: 'PATCH', url: 'v34.0/sobjects/Object/extIdField__c/456', richInput: {
59
+ name: 'test'
60
+ } }
61
+ ], haltOnError: halt_on_error }.to_json).
62
+ and_return(response)
63
+
64
+ client.send(method) do |subrequests|
65
+ subrequests.upsert('Object', 'extIdField__c',
66
+ extIdField__c: '456', name: 'test')
67
+ end
68
+ end
69
+
70
+ it 'multiple subrequests' do
71
+ client.
72
+ should_receive(:api_post).
73
+ with(endpoint, { batchRequests: [
74
+ { method: 'POST', url: 'v34.0/sobjects/Object', richInput: {
75
+ name: 'test'
76
+ } },
77
+ { method: 'PATCH', url: "v34.0/sobjects/Object/123", richInput: {
78
+ name: 'test'
79
+ } },
80
+ { method: 'DELETE', url: "v34.0/sobjects/Object/123" }
81
+ ], haltOnError: halt_on_error }.to_json).
82
+ and_return(response)
83
+
84
+ client.send(method) do |subrequests|
85
+ subrequests.create('Object', name: 'test')
86
+ subrequests.update('Object', id: '123', name: 'test')
87
+ subrequests.destroy('Object', '123')
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '#batch' do
93
+ let(:method) { :batch }
94
+ let(:halt_on_error) { false }
95
+ let(:response) { double('Faraday::Response', body: { 'results' => [] }) }
96
+ it_behaves_like 'batched requests'
97
+ end
98
+
99
+ describe '#batch!' do
100
+ let(:method) { :batch! }
101
+ let(:halt_on_error) { true }
102
+ let(:response) {
103
+ double('Faraday::Response', body: { 'hasErrors' => false, 'results' => [] })
104
+ }
105
+ it_behaves_like 'batched requests'
106
+ end
107
+ end