require 'spec_helper' shared_examples_for Restforce::AbstractClient do describe '.list_sobjects' do requests :sobjects, fixture: 'sobject/describe_sobjects_success_response' subject { client.list_sobjects } it { should be_an Array } it { should eq ['Account'] } end describe '.describe' do context 'with no arguments' do requests :sobjects, fixture: 'sobject/describe_sobjects_success_response' subject { client.describe } it { should be_an Array } end context 'with an argument' do requests 'sobjects/Whizbang/describe', fixture: 'sobject/sobject_describe_success_response' subject { client.describe('Whizbang') } its(['name']) { should eq 'Whizbang' } end end describe '.query' do requests 'query\?q=SELECT%20some,%20fields%20FROM%20object', fixture: 'sobject/query_success_response' subject { client.query('SELECT some, fields FROM object') } it { should be_an Enumerable } end describe '.get_updated' do let(:start_date) { Time.new(2015, 8, 17, 0, 0, 0, "+02:00") } let(:end_date) { Time.new(2016, 8, 19, 0, 0, 0, "+02:00") } end_string = '2016-08-18T22:00:00Z' start_string = '2015-08-16T22:00:00Z' requests "sobjects/Whizbang/updated/\\?end=#{end_string}&start=#{start_string}", fixture: 'sobject/get_updated_success_response' subject { client.get_updated('Whizbang', start_date, end_date) } it { should be_an Enumerable } end describe '.get_deleted' do let(:start_date) { Time.new(2015, 8, 17, 0, 0, 0, "+02:00") } let(:end_date) { Time.new(2016, 8, 19, 0, 0, 0, "+02:00") } end_string = '2016-08-18T22:00:00Z' start_string = '2015-08-16T22:00:00Z' requests "sobjects/Whizbang/deleted/\\?end=#{end_string}&start=#{start_string}", fixture: 'sobject/get_deleted_success_response' subject { client.get_deleted('Whizbang', start_date, end_date) } it { should be_an Enumerable } end describe '.search' do requests 'search\?q=FIND%20%7Bbar%7D', fixture: 'sobject/search_success_response' subject { client.search('FIND {bar}') } it { should be_an Array } its(:size) { should eq 2 } end describe '.org_id' do requests 'query\?q=select%20id%20from%20Organization', fixture: 'sobject/org_query_response' subject { client.org_id } it { should eq '00Dx0000000BV7z' } end describe '.create' do context 'without multipart' do requests 'sobjects/Account', method: :post, with_body: "{\"Name\":\"Foobar\"}", fixture: 'sobject/create_success_response' subject { client.create('Account', Name: 'Foobar') } it { should eq 'some_id' } end context 'with multipart' do # rubocop:disable Metrics/LineLength requests 'sobjects/Account', method: :post, with_body: %r(----boundary_string\r\nContent-Disposition: form-data; name=\"entity_content\"\r\nContent-Type: application/json\r\n\r\n{\"Name\":\"Foobar\"}\r\n----boundary_string\r\nContent-Disposition: form-data; name=\"Blob\"; filename=\"blob.jpg\"\r\nContent-Length: 42171\r\nContent-Type: image/jpeg\r\nContent-Transfer-Encoding: binary), fixture: 'sobject/create_success_response' # rubocop:enable Metrics/LineLength subject do client.create('Account', Name: 'Foobar', Blob: Restforce::UploadIO.new( File.expand_path('../../fixtures/blob.jpg', __FILE__), 'image/jpeg' )) end it { should eq 'some_id' } end end describe '.update!' do context 'with invalid Id' do requests 'sobjects/Account/001D000000INjVe', method: :patch, with_body: "{\"Name\":\"Foobar\"}", status: 404, fixture: 'sobject/delete_error_response' let(:error) do JSON.parse(fixture('sobject/delete_error_response')) end subject do lambda do client.update!('Account', Id: '001D000000INjVe', Name: 'Foobar') end end it { should raise_error( Faraday::Error::ResourceNotFound, "#{error.first['errorCode']}: #{error.first['message']}" ) } end end describe '.update' do context 'with missing Id' do subject { lambda { client.update('Account', Name: 'Foobar') } } it { should raise_error ArgumentError, 'ID field missing from provided attributes' } end context 'with invalid Id' do requests 'sobjects/Account/001D000000INjVe', method: :patch, with_body: "{\"Name\":\"Foobar\"}", status: 404, fixture: 'sobject/delete_error_response' subject { client.update('Account', Id: '001D000000INjVe', Name: 'Foobar') } it { should be_false } end context 'with success' do requests 'sobjects/Account/001D000000INjVe', method: :patch, with_body: "{\"Name\":\"Foobar\"}" [:Id, :id, 'Id', 'id'].each do |key| context "with #{key.inspect} as the key" do subject do client.update('Account', key => '001D000000INjVe', :Name => 'Foobar') end it { should be_true } end end end end describe '.upsert!' do context 'when updated' do requests 'sobjects/Account/External__c/foobar', method: :patch, with_body: "{\"Name\":\"Foobar\"}" context 'with symbol external Id key' do subject do client.upsert!('Account', 'External__c', External__c: 'foobar', Name: 'Foobar') end it { should be_true } end context 'with string external Id key' do subject do client.upsert!('Account', 'External__c', 'External__c' => 'foobar', 'Name' => 'Foobar') end it { should be_true } end end context 'when created' do requests 'sobjects/Account/External__c/foobar', method: :patch, with_body: "{\"Name\":\"Foobar\"}", fixture: 'sobject/upsert_created_success_response' [:External__c, 'External__c', :external__c, 'external__c'].each do |key| context "with #{key.inspect} as the external id" do subject do client.upsert!('Account', 'External__c', key => 'foobar', :Name => 'Foobar') end it { should eq 'foo' } end end end end describe '.destroy!' do subject(:destroy!) { client.destroy!('Account', '001D000000INjVe') } context 'with invalid Id' do requests 'sobjects/Account/001D000000INjVe', fixture: 'sobject/delete_error_response', method: :delete, status: 404 subject { lambda { destroy! } } it { should raise_error Faraday::Error::ResourceNotFound } end context 'with success' do requests 'sobjects/Account/001D000000INjVe', method: :delete it { should be_true } end end describe '.destroy' do subject { client.destroy('Account', '001D000000INjVe') } context 'with invalid Id' do requests 'sobjects/Account/001D000000INjVe', fixture: 'sobject/delete_error_response', method: :delete, status: 404 it { should be_false } end context 'with success' do requests 'sobjects/Account/001D000000INjVe', method: :delete it { should be_true } end end describe '.find' do context 'with no external id passed' do requests 'sobjects/Account/001D000000INjVe', fixture: 'sobject/sobject_find_success_response' subject { client.find('Account', '001D000000INjVe') } it { should be_a Hash } end context 'when an external id is passed' do requests 'sobjects/Account/External_Field__c/1234', fixture: 'sobject/sobject_find_success_response' subject { client.find('Account', '1234', 'External_Field__c') } it { should be_a Hash } end end describe '.select' do context 'when no external id is specified' do context 'when no select list is specified' do requests 'sobjects/Account/1234', fixture: 'sobject/sobject_select_success_response' subject { client.select('Account', '1234', nil, nil) } it { should be_a Hash } end context 'when select list is specified' do requests 'sobjects/Account/1234\?fields=External_Field__c', fixture: 'sobject/sobject_select_success_response' subject { client.select('Account', '1234', ['External_Field__c']) } it { should be_a Hash } end end context 'when an external id is specified' do context 'when no select list is specified' do requests 'sobjects/Account/External_Field__c/1234', fixture: 'sobject/sobject_select_success_response' subject { client.select('Account', '1234', nil, 'External_Field__c') } it { should be_a Hash } end context 'when select list is specified' do requests 'sobjects/Account/External_Field__c/1234\?fields=External_Field__c', fixture: 'sobject/sobject_select_success_response' subject do client.select('Account', '1234', ['External_Field__c'], 'External_Field__c') end it { should be_a Hash } end end end describe '.authenticate!' do subject(:authenticate!) { client.authenticate! } context 'when successful' do before do @request = stub_login_request( with_body: "grant_type=password&client_id=client_id" \ "&client_secret=client_secret&username=foo" \ "&password=barsecurity_token" ).to_return(status: 200, body: fixture(:auth_success_response)) end after do expect(@request).to have_been_requested end it { should be_a Hash } end context 'when no authentication middleware is present' do before do client.stub(:authentication_middleware).and_return(nil) end it "raises an error" do expect { authenticate! }.to raise_error Restforce::AuthenticationError, 'No authentication middleware present' end end end describe '.without_caching' do let(:cache) { MockCache.new } it 'deletes the cached value before querying the API' do stub = stub_api_request( 'query\?q=SELECT%20some,%20fields%20FROM%20object', fixture: 'sobject/query_success_response' ) client.query('SELECT some, fields FROM object') expect(cache).to receive(:delete).and_call_original.ordered expect(cache).to receive(:read).and_call_original.ordered client.without_caching { client.query('SELECT some, fields FROM object') } expect(stub).to have_been_requested.twice end end describe 'authentication retries' do context 'when retries reaches 0' do before do @auth_request = stub_api_request( 'query\?q=SELECT%20some,%20fields%20FROM%20object', status: 401, fixture: 'expired_session_response' ) @query_request = stub_login_request( with_body: "grant_type=password&client_id=client_id" \ "&client_secret=client_secret&username=foo&" \ "password=barsecurity_token" ).to_return(status: 200, body: fixture(:auth_success_response)) end subject { lambda { client.query('SELECT some, fields FROM object') } } it { should raise_error Restforce::UnauthorizedError } end end describe '.query with caching' do let(:cache) { MockCache.new } before do @query = stub_api_request('query\?q=SELECT%20some,%20fields%20FROM%20object'). with(headers: { 'Authorization' => "OAuth #{oauth_token}" }). to_return(status: 401, body: fixture('expired_session_response'), headers: { 'Content-Type' => 'application/json' }).then. to_return(status: 200, body: fixture('sobject/query_success_response'), headers: { 'Content-Type' => 'application/json' }) @login = stub_login_request( with_body: "grant_type=password&client_id=client_id&client_secret=" \ "client_secret&username=foo&password=barsecurity_token" ).to_return(status: 200, body: fixture(:auth_success_response)) end after do expect(@query).to have_been_made.times(2) expect(@login).to have_been_made end subject { client.query('SELECT some, fields FROM object') } it { should be_an Enumerable } end end describe Restforce::AbstractClient do describe 'with mashify' do it_behaves_like Restforce::AbstractClient describe '.query' do context 'with pagination' do subject { client.query('SELECT some, fields FROM object').next_page } requests 'query\?q', fixture: 'sobject/query_paginated_first_page_response' requests 'query/01gD', fixture: 'sobject/query_paginated_last_page_response' it { should be_a Restforce::Collection } its('first.Text_Label') { should eq 'Last Page' } end end end describe 'without mashify', mashify: false do it_behaves_like Restforce::AbstractClient end end