sfdc 3.0.0 → 3.0.1

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -0
  3. data/Gemfile +11 -0
  4. data/Guardfile +11 -0
  5. data/LICENSE +22 -0
  6. data/README.md +88 -98
  7. data/Rakefile +10 -0
  8. data/lib/sfdc.rb +1 -0
  9. data/lib/sfdc/abstract_client.rb +1 -1
  10. data/lib/sfdc/attachment.rb +0 -2
  11. data/lib/sfdc/concerns/api.rb +1 -2
  12. data/lib/sfdc/concerns/authentication.rb +1 -2
  13. data/lib/sfdc/concerns/base.rb +5 -7
  14. data/lib/sfdc/concerns/caching.rb +1 -3
  15. data/lib/sfdc/concerns/canvas.rb +0 -2
  16. data/lib/sfdc/concerns/connection.rb +2 -3
  17. data/lib/sfdc/concerns/picklists.rb +1 -4
  18. data/lib/sfdc/concerns/streaming.rb +2 -2
  19. data/lib/sfdc/concerns/verbs.rb +0 -2
  20. data/lib/sfdc/config.rb +1 -0
  21. data/lib/sfdc/middleware/authentication.rb +4 -2
  22. data/lib/sfdc/middleware/authentication/password.rb +2 -5
  23. data/lib/sfdc/middleware/authorization.rb +1 -5
  24. data/lib/sfdc/middleware/caching.rb +0 -2
  25. data/lib/sfdc/middleware/instance_url.rb +0 -4
  26. data/lib/sfdc/middleware/mashify.rb +1 -3
  27. data/lib/sfdc/middleware/multipart.rb +6 -4
  28. data/lib/sfdc/signed_request.rb +1 -1
  29. data/lib/sfdc/tooling/client.rb +4 -6
  30. data/lib/sfdc/version.rb +2 -2
  31. data/sfdc.gemspec +27 -0
  32. data/spec/fixtures/auth_error_response.json +4 -0
  33. data/spec/fixtures/auth_success_response.json +7 -0
  34. data/spec/fixtures/blob.jpg +0 -0
  35. data/spec/fixtures/expired_session_response.json +6 -0
  36. data/spec/fixtures/reauth_success_response.json +7 -0
  37. data/spec/fixtures/refresh_error_response.json +4 -0
  38. data/spec/fixtures/refresh_success_response.json +7 -0
  39. data/spec/fixtures/services_data_success_response.json +12 -0
  40. data/spec/fixtures/sobject/create_success_response.json +5 -0
  41. data/spec/fixtures/sobject/delete_error_response.json +1 -0
  42. data/spec/fixtures/sobject/describe_sobjects_success_response.json +31 -0
  43. data/spec/fixtures/sobject/list_sobjects_success_response.json +31 -0
  44. data/spec/fixtures/sobject/org_query_response.json +11 -0
  45. data/spec/fixtures/sobject/query_aggregate_success_response.json +23 -0
  46. data/spec/fixtures/sobject/query_empty_response.json +5 -0
  47. data/spec/fixtures/sobject/query_error_response.json +6 -0
  48. data/spec/fixtures/sobject/query_paginated_first_page_response.json +14 -0
  49. data/spec/fixtures/sobject/query_paginated_last_page_response.json +13 -0
  50. data/spec/fixtures/sobject/query_success_response.json +38 -0
  51. data/spec/fixtures/sobject/recent_success_response.json +18 -0
  52. data/spec/fixtures/sobject/search_error_response.json +6 -0
  53. data/spec/fixtures/sobject/search_success_response.json +16 -0
  54. data/spec/fixtures/sobject/sobject_describe_error_response.json +6 -0
  55. data/spec/fixtures/sobject/sobject_describe_success_response.json +1429 -0
  56. data/spec/fixtures/sobject/sobject_find_error_response.json +6 -0
  57. data/spec/fixtures/sobject/sobject_find_success_response.json +29 -0
  58. data/spec/fixtures/sobject/upsert_created_success_response.json +5 -0
  59. data/spec/fixtures/sobject/upsert_error_response.json +6 -0
  60. data/spec/fixtures/sobject/upsert_multiple_error_response.json +4 -0
  61. data/spec/fixtures/sobject/upsert_updated_success_response.json +0 -0
  62. data/spec/fixtures/sobject/write_error_response.json +6 -0
  63. data/spec/integration/abstract_client_spec.rb +306 -0
  64. data/spec/integration/data/client_spec.rb +90 -0
  65. data/spec/spec_helper.rb +20 -0
  66. data/spec/support/client_integration.rb +45 -0
  67. data/spec/support/concerns.rb +18 -0
  68. data/spec/support/event_machine.rb +14 -0
  69. data/spec/support/fixture_helpers.rb +45 -0
  70. data/spec/support/matchers.rb +11 -0
  71. data/spec/support/middleware.rb +76 -0
  72. data/spec/support/mock_cache.rb +13 -0
  73. data/spec/unit/abstract_client_spec.rb +11 -0
  74. data/spec/unit/attachment_spec.rb +15 -0
  75. data/spec/unit/collection_spec.rb +52 -0
  76. data/spec/unit/concerns/api_spec.rb +244 -0
  77. data/spec/unit/concerns/authentication_spec.rb +98 -0
  78. data/spec/unit/concerns/base_spec.rb +42 -0
  79. data/spec/unit/concerns/caching_spec.rb +29 -0
  80. data/spec/unit/concerns/canvas_spec.rb +30 -0
  81. data/spec/unit/concerns/connection_spec.rb +22 -0
  82. data/spec/unit/config_spec.rb +99 -0
  83. data/spec/unit/data/client_spec.rb +10 -0
  84. data/spec/unit/mash_spec.rb +36 -0
  85. data/spec/unit/middleware/authentication/password_spec.rb +31 -0
  86. data/spec/unit/middleware/authentication/token_spec.rb +24 -0
  87. data/spec/unit/middleware/authentication_spec.rb +67 -0
  88. data/spec/unit/middleware/authorization_spec.rb +11 -0
  89. data/spec/unit/middleware/gzip_spec.rb +66 -0
  90. data/spec/unit/middleware/instance_url_spec.rb +24 -0
  91. data/spec/unit/middleware/logger_spec.rb +19 -0
  92. data/spec/unit/middleware/mashify_spec.rb +11 -0
  93. data/spec/unit/middleware/raise_error_spec.rb +32 -0
  94. data/spec/unit/signed_request_spec.rb +24 -0
  95. data/spec/unit/sobject_spec.rb +86 -0
  96. data/spec/unit/tooling/client_spec.rb +7 -0
  97. metadata +225 -65
@@ -0,0 +1,20 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'bundler/setup'
5
+ Bundler.require :default, :test
6
+ require 'faye' unless RUBY_PLATFORM == 'java'
7
+
8
+ require 'webmock/rspec'
9
+
10
+ WebMock.disable_net_connect!
11
+
12
+ RSpec.configure do |config|
13
+ config.order = 'random'
14
+ config.filter_run :focus => true
15
+ config.run_all_when_everything_filtered = true
16
+ end
17
+
18
+ # Requires supporting ruby files with custom matchers and macros, etc,
19
+ # in spec/support/ and its subdirectories.
20
+ Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each {|f| require f}
@@ -0,0 +1,45 @@
1
+ module ClientIntegrationExampleGroup
2
+ def self.included(base)
3
+ base.class_eval do
4
+ let(:oauth_token) { '00Dx0000000BV7z!AR8AQAxo9UfVkh8AlV0Gomt9Czx9LjHnSSpwBMmbRcgKFmxOtvxjTrKW19ye6PE3Ds1eQz3z8jr3W7_VbWmEu4Q8TVGSTHxs' }
5
+ let(:refresh_token) { 'refresh' }
6
+ let(:instance_url) { 'https://na1.salesforce.com' }
7
+ let(:username) { 'foo' }
8
+ let(:password) { 'bar' }
9
+ let(:security_token) { 'security_token' }
10
+ let(:client_id) { 'client_id' }
11
+ let(:client_secret) { 'client_secret' }
12
+ let(:cache) { nil }
13
+
14
+ let(:base_options) do
15
+ {
16
+ :oauth_token => oauth_token,
17
+ :refresh_token => refresh_token,
18
+ :instance_url => instance_url,
19
+ :username => username,
20
+ :password => password,
21
+ :security_token => security_token,
22
+ :client_id => client_id,
23
+ :client_secret => client_secret,
24
+ :cache => cache
25
+ }
26
+ end
27
+
28
+ let(:client_options) { base_options }
29
+
30
+ subject(:client) { described_class.new client_options }
31
+ end
32
+ end
33
+
34
+ RSpec.configure do |config|
35
+ config.include self,
36
+ :example_group => {
37
+ :describes => lambda { |described| described <= Sfdc::AbstractClient },
38
+ :file_path => %r{spec/integration}
39
+ }
40
+
41
+ config.before :mashify => false do
42
+ client.middleware.delete(Sfdc::Middleware::Mashify)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ module ConcernsExampleGroup
2
+ def self.included(base)
3
+ base.class_eval do
4
+ let(:klass) do
5
+ context = self
6
+ Class.new { include context.described_class }
7
+ end
8
+
9
+ let(:client) { klass.new }
10
+
11
+ subject { client }
12
+ end
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+ config.include self, :example_group => { :file_path => %r{spec/unit/concerns} }
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ RSpec.configure do |config|
2
+ config.before do
3
+ EventMachine.stub(:connect) if defined?(EventMachine)
4
+ end
5
+
6
+ config.filter_run_excluding :event_machine => true if RUBY_PLATFORM == 'java'
7
+
8
+ config.around :event_machine => true do |example|
9
+ EM.run {
10
+ example.run
11
+ EM.stop
12
+ }
13
+ end
14
+ end
@@ -0,0 +1,45 @@
1
+ module FixtureHelpers
2
+ module InstanceMethods
3
+
4
+ def stub_api_request(endpoint, options={})
5
+ options = {
6
+ :method => :get,
7
+ :status => 200,
8
+ :api_version => Sfdc.configuration.api_version
9
+ }.merge(options)
10
+
11
+ stub = stub_request(options[:method], %r{/services/data/v#{options[:api_version]}/#{endpoint}})
12
+ stub = stub.with(:body => options[:with_body]) if options[:with_body] && !RUBY_VERSION.match(/^1.8/)
13
+ stub = stub.to_return(:status => options[:status], :body => fixture(options[:fixture]), :headers => { 'Content-Type' => 'application/json'}) if options[:fixture]
14
+ stub
15
+ end
16
+
17
+ def stub_login_request(options={})
18
+ stub = stub_request(:post, "https://login.salesforce.com/services/oauth2/token")
19
+ stub = stub.with(:body => options[:with_body]) if options[:with_body] && !RUBY_VERSION.match(/^1.8/)
20
+ stub
21
+ end
22
+
23
+ def fixture(f)
24
+ File.read(File.expand_path("../../fixtures/#{f}.json", __FILE__))
25
+ end
26
+
27
+ end
28
+
29
+ module ClassMethods
30
+ def requests(endpoint, options={})
31
+ before do
32
+ (@requests ||= []) << stub_api_request(endpoint, options)
33
+ end
34
+
35
+ after do
36
+ @requests.each { |request| expect(request).to have_been_requested }
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ RSpec.configure do |config|
43
+ config.include FixtureHelpers::InstanceMethods
44
+ config.extend FixtureHelpers::ClassMethods
45
+ end
@@ -0,0 +1,11 @@
1
+ RSpec::Matchers.define :include_picklist_values do |expected|
2
+ match do |actual|
3
+ actual.all? { |picklist_value| expected.include? picklist_value['value'] }
4
+ end
5
+ end
6
+
7
+ RSpec::Matchers.define :have_client do |expected|
8
+ match do |actual|
9
+ actual.instance_variable_get(:@client) == expected
10
+ end
11
+ end
@@ -0,0 +1,76 @@
1
+ module MiddlewareExampleGroup
2
+ def self.included(base)
3
+ base.class_eval do
4
+ let(:app) { double('@app', :call => nil) }
5
+ let(:env) { { :request_headers => {}, :response_headers => {} } }
6
+ let(:retries) { 3 }
7
+ let(:options) { { } }
8
+ let(:client) { double(Sfdc::AbstractClient) }
9
+ let(:auth_callback) { double(Proc) }
10
+ let(:success_response) { Sfdc::Mash.new(JSON.parse(fixture(:auth_success_response))) }
11
+ subject(:middleware) { described_class.new app, client, options }
12
+ end
13
+ end
14
+
15
+ RSpec.configure do |config|
16
+ config.include self,
17
+ :example_group => { :file_path => %r{spec/unit/middleware} }
18
+ end
19
+ end
20
+
21
+ shared_examples_for 'authentication middleware' do
22
+ describe '.authenticate!' do
23
+ after do
24
+ request.should have_been_requested
25
+ end
26
+
27
+ context 'when successful' do
28
+ let!(:request) { success_request }
29
+
30
+ describe '@options' do
31
+ subject { options }
32
+
33
+ before do
34
+ middleware.authenticate!
35
+ end
36
+
37
+ its([:instance_url]) { should eq 'https://na1.salesforce.com' }
38
+ its([:oauth_token]) { should eq '00Dx0000000BV7z!AR8AQAxo9UfVkh8AlV0Gomt9Czx9LjHnSSpwBMmbRcgKFmxOtvxjTrKW19ye6PE3Ds1eQz3z8jr3W7_VbWmEu4Q8TVGSTHxs' }
39
+ end
40
+
41
+ context 'when an authentication_callback is specified' do
42
+ before(:each) do
43
+ options.merge!(:authentication_callback => auth_callback)
44
+ end
45
+
46
+ it 'calls the authentication callback with the response body' do
47
+ auth_callback.should_receive(:call).with(success_response)
48
+ middleware.authenticate!
49
+ end
50
+ end
51
+ end
52
+
53
+ context 'when unsuccessful' do
54
+ let!(:request) { fail_request }
55
+
56
+ it 'raises an exception' do
57
+ expect {
58
+ middleware.authenticate!
59
+ }.to raise_error Sfdc::AuthenticationError, /^invalid_grant: .*/
60
+ end
61
+
62
+ context 'when an authentication_callback is specified' do
63
+ before(:each) do
64
+ options.merge!(:authentication_callback => auth_callback)
65
+ end
66
+
67
+ it 'does not call the authentication callback' do
68
+ auth_callback.should_not_receive(:call)
69
+ expect do
70
+ middleware.authenticate!
71
+ end.to raise_error
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,13 @@
1
+ class MockCache
2
+ def initialize
3
+ @storage = {}
4
+ end
5
+
6
+ def fetch(key, &block)
7
+ @storage[key] ||= block.call
8
+ end
9
+
10
+ def delete(key)
11
+ @storage.delete(key)
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sfdc::AbstractClient do
4
+ subject { described_class }
5
+
6
+ it { should < Sfdc::Concerns::Base }
7
+ it { should < Sfdc::Concerns::Connection }
8
+ it { should < Sfdc::Concerns::Authentication }
9
+ it { should < Sfdc::Concerns::Caching }
10
+ it { should < Sfdc::Concerns::API }
11
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sfdc::Attachment do
4
+ let(:client) { double(Sfdc::AbstractClient) }
5
+ let(:body_url) { '/services/data/v26.0/sobjects/Attachment/00PG0000006Hll5MAC/Body' }
6
+ let(:hash) { { 'Id' => '1234', 'Body' => body_url } }
7
+ let(:sobject) { described_class.new(hash, client) }
8
+
9
+ describe '.Body' do
10
+ it 'requests the body' do
11
+ client.should_receive(:get).with(body_url).and_return(double('response').as_null_object)
12
+ sobject.Body
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sfdc::Collection do
4
+ let(:client) { double(Sfdc::AbstractClient) }
5
+
6
+ describe '#new' do
7
+ context 'without pagination' do
8
+ subject(:collection) do
9
+ described_class.new(JSON.parse(fixture('sobject/query_success_response')), client)
10
+ end
11
+
12
+ it { should respond_to :each }
13
+ its(:size) { should eq 1 }
14
+ its(:has_next_page?) { should be_false }
15
+ it { should have_client client }
16
+
17
+ describe 'each record' do
18
+ it { should be_all { |record| expect(record).to be_a Sfdc::SObject } }
19
+ end
20
+ end
21
+
22
+ context 'with pagination' do
23
+ let(:first_page) { JSON.parse(fixture('sobject/query_paginated_first_page_response')) }
24
+ let(:next_page) { JSON.parse(fixture('sobject/query_paginated_last_page_response')) }
25
+ subject(:collection) { described_class.new(first_page, client) }
26
+
27
+ it { should respond_to :each }
28
+ it { should have_client client }
29
+
30
+ context 'when only values from the first page are being requested' do
31
+ before { client.should_receive(:get).never }
32
+
33
+ its(:size) { should eq 2 }
34
+ its(:first) { should be_a Sfdc::SObject }
35
+ its(:current_page) { should be_a Array }
36
+ its(:current_page) { should have(1).element }
37
+ end
38
+
39
+ context 'when all of the values are being requested' do
40
+ before do
41
+ client.stub(:get).
42
+ and_return(double(:body => Sfdc::Collection.new(next_page, client)))
43
+ end
44
+
45
+ its(:pages) { should be_all { |page| expect(page).to be_a Sfdc::Collection } }
46
+ its(:has_next_page?) { should be_true }
47
+ it { should be_all { |record| expect(record).to be_a Sfdc::SObject } }
48
+ its(:next_page) { should be_a Sfdc::Collection }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,244 @@
1
+ require 'spec_helper'
2
+
3
+ describe Sfdc::Concerns::API do
4
+ let(:response) { double('Faraday::Response', :body => double('Body')) }
5
+
6
+ describe '.list_sobjects' do
7
+ subject { client.list_sobjects }
8
+
9
+ before do
10
+ client.stub :describe => [ { 'name' => 'foo' } ]
11
+ end
12
+
13
+ it { should eq ['foo'] }
14
+ end
15
+
16
+ describe '.describe' do
17
+ subject(:describe) { client.describe }
18
+
19
+ it 'returns the global describe' do
20
+ sobjects = double('sobjects')
21
+ response.body.stub(:[]).with('sobjects').and_return(sobjects)
22
+ client.should_receive(:api_get).
23
+ with('sobjects').
24
+ and_return(response)
25
+ expect(describe).to eq sobjects
26
+ end
27
+
28
+ context 'when given the name of an sobject' do
29
+ subject(:describe) { client.describe('Whizbang') }
30
+
31
+ it 'returns the full describe' do
32
+ client.should_receive(:api_get).
33
+ with('sobjects/Whizbang/describe').
34
+ and_return(response)
35
+ expect(describe).to eq response.body
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '.describe_layouts' do
41
+ subject(:describe_layouts) { client.describe_layouts('Whizbang') }
42
+
43
+ it 'returns the layouts for the sobject' do
44
+ client.should_receive(:api_get).
45
+ with('sobjects/Whizbang/describe/layouts').
46
+ and_return(response)
47
+ expect(describe_layouts).to eq response.body
48
+ end
49
+
50
+ context 'when given the id of a layout' do
51
+ subject(:describe_layouts) { client.describe_layouts('Whizbang', '012E0000000RHEp') }
52
+
53
+ it 'returns the describe for the specified layout' do
54
+ client.should_receive(:api_get).
55
+ with('sobjects/Whizbang/describe/layouts/012E0000000RHEp').
56
+ and_return(response)
57
+ expect(describe_layouts).to eq response.body
58
+ end
59
+ end
60
+ end
61
+
62
+ describe '.org_id' do
63
+ subject(:org_id) { client.org_id }
64
+
65
+ it 'returns the organization id' do
66
+ organizations = [ { 'Id' => 'foo' } ]
67
+ client.should_receive(:query).
68
+ with('select id from Organization').
69
+ and_return(organizations)
70
+ expect(org_id).to eq 'foo'
71
+ end
72
+ end
73
+
74
+ describe '.query' do
75
+ let(:soql) { 'Select Id from Account' }
76
+ subject(:results) { client.query(soql) }
77
+
78
+ context 'with mashify middleware' do
79
+ before do
80
+ client.stub :mashify? => true
81
+ end
82
+
83
+ it 'returns the body' do
84
+ client.should_receive(:api_get).
85
+ with('query', :q => soql).
86
+ and_return(response)
87
+ expect(results).to eq response.body
88
+ end
89
+ end
90
+
91
+ context 'without mashify middleware' do
92
+ before do
93
+ client.stub :mashify? => false
94
+ end
95
+
96
+ it 'returns the records attribute of the body' do
97
+ records = double('records')
98
+ response.body.stub(:[]).
99
+ with('records').
100
+ and_return(records)
101
+ client.should_receive(:api_get).
102
+ with('query', :q => soql).
103
+ and_return(response)
104
+ expect(results).to eq records
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '.search' do
110
+ let(:sosl) { 'FIND {bar}' }
111
+ subject(:results) { client.search(sosl) }
112
+
113
+ it 'performs a sosl search' do
114
+ client.should_receive(:api_get).
115
+ with('search', :q => sosl).
116
+ and_return(response)
117
+ expect(results).to eq response.body
118
+ end
119
+ end
120
+
121
+ [:create, :update, :upsert, :destroy].each do |method|
122
+ describe ".#{method}" do
123
+ let(:args) { [] }
124
+ subject(:result) { client.send(method, *args) }
125
+
126
+ it "delegates to :#{method}!" do
127
+ client.should_receive(:"#{method}!").
128
+ with(*args).
129
+ and_return(response)
130
+ expect(result).to eq response
131
+ end
132
+
133
+ it 'rescues exceptions' do
134
+ [Faraday::Error::ClientError].each do |exception_klass|
135
+ client.should_receive(:"#{method}!").
136
+ with(*args).
137
+ and_raise(exception_klass.new(nil))
138
+ expect(result).to eq false
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ describe '.create!' do
145
+ let(:sobject) { 'Whizbang' }
146
+ let(:attrs) { Hash.new }
147
+ subject(:result) { client.create!(sobject, attrs) }
148
+
149
+ it 'send an HTTP POST, and returns the id of the record' do
150
+ response.body.stub(:[]).with('id').and_return('1234')
151
+ client.should_receive(:api_post).
152
+ with('sobjects/Whizbang', attrs).
153
+ and_return(response)
154
+ expect(result).to eq '1234'
155
+ end
156
+ end
157
+
158
+ describe '.update!' do
159
+ let(:sobject) { 'Whizbang' }
160
+ let(:attrs) { Hash.new }
161
+ subject(:result) { client.update!(sobject, attrs) }
162
+
163
+ context 'when the id field is present' do
164
+ let(:attrs) { { :id => '1234' } }
165
+
166
+ it 'sends an HTTP PATCH, and returns true' do
167
+ client.should_receive(:api_patch).
168
+ with('sobjects/Whizbang/1234', attrs)
169
+ expect(result).to be_true
170
+ end
171
+ end
172
+
173
+ context 'when the id field is missing from the attrs' do
174
+ subject { lambda { result }}
175
+ it { should raise_error ArgumentError, 'Id field missing from attrs.' }
176
+ end
177
+ end
178
+
179
+ describe '.upsert!' do
180
+ let(:sobject) { 'Whizbang' }
181
+ let(:field) { :External_ID__c }
182
+ let(:attrs) { { 'External_ID__c' => '1234' } }
183
+ subject(:result) { client.upsert!(sobject, field, attrs) }
184
+
185
+ context 'when the record is found and updated' do
186
+ it 'returns true' do
187
+ response.body.stub :[]
188
+ client.should_receive(:api_patch).
189
+ with('sobjects/Whizbang/External_ID__c/1234', {}).
190
+ and_return(response)
191
+ expect(result).to be_true
192
+ end
193
+ end
194
+
195
+ context 'when the record is found and created' do
196
+ it 'returns the id of the record' do
197
+ response.body.stub(:[]).with('id').and_return('4321')
198
+ client.should_receive(:api_patch).
199
+ with('sobjects/Whizbang/External_ID__c/1234', {}).
200
+ and_return(response)
201
+ expect(result).to eq '4321'
202
+ end
203
+ end
204
+ end
205
+
206
+ describe '.destroy!' do
207
+ let(:id) { '1234' }
208
+ let(:sobject) { 'Whizbang' }
209
+ subject(:result) { client.destroy!(sobject, id) }
210
+
211
+ it 'sends and HTTP delete, and returns true' do
212
+ client.should_receive(:api_delete).
213
+ with('sobjects/Whizbang/1234')
214
+ expect(result).to be_true
215
+ end
216
+ end
217
+
218
+ describe '.find' do
219
+ let(:sobject) { 'Whizbang' }
220
+ let(:id) { '1234' }
221
+ let(:field) { nil }
222
+ subject(:result) { client.find(sobject, id, field) }
223
+
224
+ context 'when no external id is specified' do
225
+ it 'returns the full representation of the object' do
226
+ client.should_receive(:api_get).
227
+ with('sobjects/Whizbang/1234').
228
+ and_return(response)
229
+ expect(result).to eq response.body
230
+ end
231
+ end
232
+
233
+ context 'when an external id is specified' do
234
+ let(:field) { :External_ID__c }
235
+
236
+ it 'returns the full representation of the object' do
237
+ client.should_receive(:api_get).
238
+ with('sobjects/Whizbang/External_ID__c/1234').
239
+ and_return(response)
240
+ expect(result).to eq response.body
241
+ end
242
+ end
243
+ end
244
+ end