force 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +96 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +107 -0
  5. data/Guardfile +8 -0
  6. data/LICENSE +22 -0
  7. data/README.md +421 -0
  8. data/Rakefile +10 -0
  9. data/coverage/assets/0.7.1/application.css +1110 -0
  10. data/coverage/assets/0.7.1/application.js +626 -0
  11. data/coverage/assets/0.7.1/fancybox/blank.gif +0 -0
  12. data/coverage/assets/0.7.1/fancybox/fancy_close.png +0 -0
  13. data/coverage/assets/0.7.1/fancybox/fancy_loading.png +0 -0
  14. data/coverage/assets/0.7.1/fancybox/fancy_nav_left.png +0 -0
  15. data/coverage/assets/0.7.1/fancybox/fancy_nav_right.png +0 -0
  16. data/coverage/assets/0.7.1/fancybox/fancy_shadow_e.png +0 -0
  17. data/coverage/assets/0.7.1/fancybox/fancy_shadow_n.png +0 -0
  18. data/coverage/assets/0.7.1/fancybox/fancy_shadow_ne.png +0 -0
  19. data/coverage/assets/0.7.1/fancybox/fancy_shadow_nw.png +0 -0
  20. data/coverage/assets/0.7.1/fancybox/fancy_shadow_s.png +0 -0
  21. data/coverage/assets/0.7.1/fancybox/fancy_shadow_se.png +0 -0
  22. data/coverage/assets/0.7.1/fancybox/fancy_shadow_sw.png +0 -0
  23. data/coverage/assets/0.7.1/fancybox/fancy_shadow_w.png +0 -0
  24. data/coverage/assets/0.7.1/fancybox/fancy_title_left.png +0 -0
  25. data/coverage/assets/0.7.1/fancybox/fancy_title_main.png +0 -0
  26. data/coverage/assets/0.7.1/fancybox/fancy_title_over.png +0 -0
  27. data/coverage/assets/0.7.1/fancybox/fancy_title_right.png +0 -0
  28. data/coverage/assets/0.7.1/fancybox/fancybox-x.png +0 -0
  29. data/coverage/assets/0.7.1/fancybox/fancybox-y.png +0 -0
  30. data/coverage/assets/0.7.1/fancybox/fancybox.png +0 -0
  31. data/coverage/assets/0.7.1/favicon_green.png +0 -0
  32. data/coverage/assets/0.7.1/favicon_red.png +0 -0
  33. data/coverage/assets/0.7.1/favicon_yellow.png +0 -0
  34. data/coverage/assets/0.7.1/loading.gif +0 -0
  35. data/coverage/assets/0.7.1/magnify.png +0 -0
  36. data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  37. data/coverage/assets/0.7.1/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  38. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  39. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  40. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  41. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  42. data/coverage/assets/0.7.1/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  43. data/coverage/assets/0.7.1/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  44. data/coverage/assets/0.7.1/smoothness/images/ui-icons_222222_256x240.png +0 -0
  45. data/coverage/assets/0.7.1/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  46. data/coverage/assets/0.7.1/smoothness/images/ui-icons_454545_256x240.png +0 -0
  47. data/coverage/assets/0.7.1/smoothness/images/ui-icons_888888_256x240.png +0 -0
  48. data/coverage/assets/0.7.1/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  49. data/coverage/index.html +19808 -0
  50. data/force.gemspec +27 -0
  51. data/lib/force.rb +74 -0
  52. data/lib/force/abstract_client.rb +9 -0
  53. data/lib/force/attachment.rb +21 -0
  54. data/lib/force/client.rb +3 -0
  55. data/lib/force/collection.rb +45 -0
  56. data/lib/force/concerns/api.rb +321 -0
  57. data/lib/force/concerns/authentication.rb +39 -0
  58. data/lib/force/concerns/base.rb +59 -0
  59. data/lib/force/concerns/caching.rb +24 -0
  60. data/lib/force/concerns/canvas.rb +10 -0
  61. data/lib/force/concerns/connection.rb +74 -0
  62. data/lib/force/concerns/picklists.rb +87 -0
  63. data/lib/force/concerns/streaming.rb +31 -0
  64. data/lib/force/concerns/verbs.rb +67 -0
  65. data/lib/force/config.rb +140 -0
  66. data/lib/force/data/client.rb +18 -0
  67. data/lib/force/mash.rb +66 -0
  68. data/lib/force/middleware.rb +27 -0
  69. data/lib/force/middleware/authentication.rb +73 -0
  70. data/lib/force/middleware/authentication/password.rb +17 -0
  71. data/lib/force/middleware/authentication/token.rb +15 -0
  72. data/lib/force/middleware/authorization.rb +15 -0
  73. data/lib/force/middleware/caching.rb +22 -0
  74. data/lib/force/middleware/gzip.rb +31 -0
  75. data/lib/force/middleware/instance_url.rb +14 -0
  76. data/lib/force/middleware/logger.rb +40 -0
  77. data/lib/force/middleware/mashify.rb +16 -0
  78. data/lib/force/middleware/multipart.rb +55 -0
  79. data/lib/force/middleware/raise_error.rb +25 -0
  80. data/lib/force/signed_request.rb +48 -0
  81. data/lib/force/sobject.rb +68 -0
  82. data/lib/force/tooling/client.rb +11 -0
  83. data/lib/force/upload_io.rb +20 -0
  84. data/lib/force/version.rb +3 -0
  85. data/spec/fixtures/auth_error_response.json +4 -0
  86. data/spec/fixtures/auth_success_response.json +7 -0
  87. data/spec/fixtures/blob.jpg +0 -0
  88. data/spec/fixtures/expired_session_response.json +6 -0
  89. data/spec/fixtures/reauth_success_response.json +7 -0
  90. data/spec/fixtures/refresh_error_response.json +4 -0
  91. data/spec/fixtures/refresh_success_response.json +7 -0
  92. data/spec/fixtures/services_data_success_response.json +12 -0
  93. data/spec/fixtures/sobject/create_success_response.json +5 -0
  94. data/spec/fixtures/sobject/delete_error_response.json +1 -0
  95. data/spec/fixtures/sobject/describe_sobjects_success_response.json +31 -0
  96. data/spec/fixtures/sobject/list_sobjects_success_response.json +31 -0
  97. data/spec/fixtures/sobject/org_query_response.json +11 -0
  98. data/spec/fixtures/sobject/query_aggregate_success_response.json +23 -0
  99. data/spec/fixtures/sobject/query_empty_response.json +5 -0
  100. data/spec/fixtures/sobject/query_error_response.json +6 -0
  101. data/spec/fixtures/sobject/query_paginated_first_page_response.json +14 -0
  102. data/spec/fixtures/sobject/query_paginated_last_page_response.json +13 -0
  103. data/spec/fixtures/sobject/query_success_response.json +38 -0
  104. data/spec/fixtures/sobject/recent_success_response.json +18 -0
  105. data/spec/fixtures/sobject/search_error_response.json +6 -0
  106. data/spec/fixtures/sobject/search_success_response.json +16 -0
  107. data/spec/fixtures/sobject/sobject_describe_error_response.json +6 -0
  108. data/spec/fixtures/sobject/sobject_describe_success_response.json +1429 -0
  109. data/spec/fixtures/sobject/sobject_find_error_response.json +6 -0
  110. data/spec/fixtures/sobject/sobject_find_success_response.json +29 -0
  111. data/spec/fixtures/sobject/upsert_created_success_response.json +5 -0
  112. data/spec/fixtures/sobject/upsert_error_response.json +6 -0
  113. data/spec/fixtures/sobject/upsert_multiple_error_response.json +4 -0
  114. data/spec/fixtures/sobject/upsert_updated_success_response.json +0 -0
  115. data/spec/fixtures/sobject/write_error_response.json +6 -0
  116. data/spec/integration/abstract_client_spec.rb +306 -0
  117. data/spec/integration/data/client_spec.rb +90 -0
  118. data/spec/spec_helper.rb +20 -0
  119. data/spec/support/client_integration.rb +45 -0
  120. data/spec/support/concerns.rb +18 -0
  121. data/spec/support/event_machine.rb +14 -0
  122. data/spec/support/fixture_helpers.rb +45 -0
  123. data/spec/support/matchers.rb +11 -0
  124. data/spec/support/middleware.rb +76 -0
  125. data/spec/support/mock_cache.rb +13 -0
  126. data/spec/unit/abstract_client_spec.rb +11 -0
  127. data/spec/unit/attachment_spec.rb +15 -0
  128. data/spec/unit/collection_spec.rb +52 -0
  129. data/spec/unit/concerns/api_spec.rb +244 -0
  130. data/spec/unit/concerns/authentication_spec.rb +98 -0
  131. data/spec/unit/concerns/base_spec.rb +42 -0
  132. data/spec/unit/concerns/caching_spec.rb +29 -0
  133. data/spec/unit/concerns/canvas_spec.rb +30 -0
  134. data/spec/unit/concerns/connection_spec.rb +22 -0
  135. data/spec/unit/config_spec.rb +99 -0
  136. data/spec/unit/data/client_spec.rb +10 -0
  137. data/spec/unit/mash_spec.rb +36 -0
  138. data/spec/unit/middleware/authentication/password_spec.rb +31 -0
  139. data/spec/unit/middleware/authentication/token_spec.rb +24 -0
  140. data/spec/unit/middleware/authentication_spec.rb +67 -0
  141. data/spec/unit/middleware/authorization_spec.rb +11 -0
  142. data/spec/unit/middleware/gzip_spec.rb +66 -0
  143. data/spec/unit/middleware/instance_url_spec.rb +24 -0
  144. data/spec/unit/middleware/logger_spec.rb +19 -0
  145. data/spec/unit/middleware/mashify_spec.rb +11 -0
  146. data/spec/unit/middleware/raise_error_spec.rb +32 -0
  147. data/spec/unit/signed_request_spec.rb +24 -0
  148. data/spec/unit/sobject_spec.rb +86 -0
  149. data/spec/unit/tooling/client_spec.rb +7 -0
  150. data/tmp/rspec_guard_result +1 -0
  151. metadata +383 -0
@@ -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 <= Force::AbstractClient },
38
+ :file_path => %r{spec/integration}
39
+ }
40
+
41
+ config.before :mashify => false do
42
+ client.middleware.delete(Force::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 => Force.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(Force::AbstractClient) }
9
+ let(:auth_callback) { double(Proc) }
10
+ let(:success_response) { Force::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 Force::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 Force::AbstractClient do
4
+ subject { described_class }
5
+
6
+ it { should < Force::Concerns::Base }
7
+ it { should < Force::Concerns::Connection }
8
+ it { should < Force::Concerns::Authentication }
9
+ it { should < Force::Concerns::Caching }
10
+ it { should < Force::Concerns::API }
11
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Force::Attachment do
4
+ let(:client) { double(Force::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 Force::Collection do
4
+ let(:client) { double(Force::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 Force::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 Force::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 => Force::Collection.new(next_page, client)))
43
+ end
44
+
45
+ its(:pages) { should be_all { |page| expect(page).to be_a Force::Collection } }
46
+ its(:has_next_page?) { should be_true }
47
+ it { should be_all { |record| expect(record).to be_a Force::SObject } }
48
+ its(:next_page) { should be_a Force::Collection }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,244 @@
1
+ require 'spec_helper'
2
+
3
+ describe Force::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