executrix 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ require 'csv'
2
+
3
+ module Executrix
4
+ module Helper
5
+ extend self
6
+
7
+ CSV_OPTIONS = {
8
+ col_sep: ',',
9
+ quote_char: '"',
10
+ force_quotes: true,
11
+ }
12
+
13
+ def records_to_csv records
14
+ file_mock = StringIO.new
15
+ csv_client = CSV.new(file_mock, CSV_OPTIONS)
16
+ all_headers = []
17
+ all_rows = []
18
+ records.each do |hash|
19
+ row = CSV::Row.new([],[],false)
20
+ to_store = hash.inject({}) do |h, (k, v)|
21
+ h[k] = v.class == Array ? v.join(';') : v
22
+ h
23
+ end
24
+ row << to_store
25
+ all_headers << row.headers
26
+ all_rows << row
27
+ end
28
+ all_headers.flatten!.uniq!
29
+ csv_client << all_headers
30
+ all_rows.each do |row|
31
+ csv_client << row.fields(*all_headers)
32
+ end
33
+ file_mock.string
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,214 @@
1
+ require 'net/https'
2
+ require 'nori'
3
+ require 'csv'
4
+
5
+ module Executrix
6
+ module Http
7
+ extend self
8
+
9
+ def login *args
10
+ r = Http::Request.login(*args)
11
+ process_soap_response(nori.parse(process_http_request(r)))
12
+ end
13
+
14
+ def create_job *args
15
+ r = Http::Request.create_job(*args)
16
+ process_xml_response(nori.parse(process_http_request(r)))
17
+ end
18
+
19
+ def close_job *args
20
+ r = Http::Request.close_job(*args)
21
+ process_xml_response(nori.parse(process_http_request(r)))
22
+ end
23
+
24
+ def add_batch *args
25
+ r = Http::Request.add_batch(*args)
26
+ process_xml_response(nori.parse(process_http_request(r)))
27
+ end
28
+
29
+ def query_batch *args
30
+ r = Http::Request.query_batch(*args)
31
+ process_xml_response(nori.parse(process_http_request(r)))
32
+ end
33
+
34
+ def query_batch_result_id *args
35
+ r = Http::Request.query_batch_result_id(*args)
36
+ process_xml_response(nori.parse(process_http_request(r)))
37
+ end
38
+
39
+ def query_batch_result_data *args
40
+ r = Http::Request.query_batch_result_data(*args)
41
+ process_csv_response(process_http_request(r))
42
+ end
43
+
44
+ def process_http_request(r)
45
+ http = Net::HTTP.new(r.host, 443)
46
+ http.use_ssl = true
47
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
48
+ http_request = Net::HTTP.
49
+ const_get(r.http_method.capitalize).
50
+ new(r.path, r.headers)
51
+ http_request.body = r.body if r.body
52
+ http.request(http_request).body
53
+ end
54
+
55
+ private
56
+ def nori
57
+ Nori.new(
58
+ :advanced_typecasting => true,
59
+ :strip_namespaces => true,
60
+ :convert_tags_to => lambda { |tag| tag.snakecase.to_sym })
61
+ end
62
+
63
+ def process_xml_response res
64
+ if res[:error]
65
+ raise "#{res[:error][:exception_code]}: #{res[:error][:exception_message]}"
66
+ end
67
+
68
+ res.values.first
69
+ end
70
+
71
+ def process_csv_response res
72
+ CSV.parse(res.gsub(/\n\s+/, "\n"), headers: true).map{|r| r.to_hash}
73
+ end
74
+
75
+ def process_soap_response res
76
+ raw_result = res.fetch(:body){res.fetch(:envelope).fetch(:body)}
77
+ raise raw_result[:fault][:faultstring] if raw_result[:fault]
78
+
79
+ login_result = raw_result[:login_response][:result]
80
+ instance = login_result[:server_url][/^https?:\/\/(\w+)(-api)?/, 1]
81
+ login_result.merge(instance: instance)
82
+ end
83
+
84
+ class Request
85
+ attr_reader :path
86
+ attr_reader :host
87
+ attr_reader :body
88
+ attr_reader :headers
89
+ attr_reader :http_method
90
+
91
+ def initialize http_method, host, path, body, headers
92
+ @http_method = http_method
93
+ @host = host
94
+ @path = path
95
+ @body = body
96
+ @headers = headers
97
+ end
98
+
99
+ def self.login sandbox, username, password, api_version
100
+ body = %Q{<?xml version="1.0" encoding="utf-8" ?>
101
+ <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
102
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
103
+ xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
104
+ <env:Body>
105
+ <n1:login xmlns:n1="urn:partner.soap.sforce.com">
106
+ <n1:username>#{username}</n1:username>
107
+ <n1:password>#{password}</n1:password>
108
+ </n1:login>
109
+ </env:Body>
110
+ </env:Envelope>}
111
+ headers = {
112
+ 'Content-Type' => 'text/xml; charset=utf-8',
113
+ 'SOAPAction' => 'login'
114
+ }
115
+ host = sandbox ? 'test.salesforce.com' : 'login.salesforce.com'
116
+ Http::Request.new(
117
+ :post,
118
+ host,
119
+ "/services/Soap/u/#{api_version}",
120
+ body,
121
+ headers)
122
+ end
123
+
124
+ def self.create_job instance, session_id, operation, sobject, api_version, external_field = nil
125
+ external_field_line = external_field ?
126
+ "<externalIdFieldName>#{external_field}</externalIdFieldName>" : nil
127
+ body = %Q{<?xml version="1.0" encoding="utf-8" ?>
128
+ <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
129
+ <operation>#{operation}</operation>
130
+ <object>#{sobject}</object>
131
+ #{external_field_line}
132
+ <contentType>CSV</contentType>
133
+ </jobInfo>
134
+ }
135
+ headers = {
136
+ 'Content-Type' => 'application/xml; charset=utf-8',
137
+ 'X-SFDC-Session' => session_id}
138
+ Http::Request.new(
139
+ :post,
140
+ "#{instance}.salesforce.com",
141
+ "/services/async/#{api_version}/job",
142
+ body,
143
+ headers)
144
+ end
145
+
146
+ def self.close_job instance, session_id, job_id, api_version
147
+ body = %Q{<?xml version="1.0" encoding="utf-8" ?>
148
+ <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
149
+ <state>Closed</state>
150
+ </jobInfo>
151
+ }
152
+ headers = {
153
+ 'Content-Type' => 'application/xml; charset=utf-8',
154
+ 'X-SFDC-Session' => session_id}
155
+ Http::Request.new(
156
+ :post,
157
+ "#{instance}.salesforce.com",
158
+ "/services/async/#{api_version}/job/#{job_id}",
159
+ body,
160
+ headers)
161
+ end
162
+
163
+ def self.add_batch instance, session_id, job_id, data, api_version
164
+ headers = {'Content-Type' => 'text/csv; charset=UTF-8', 'X-SFDC-Session' => session_id}
165
+ Http::Request.new(
166
+ :post,
167
+ "#{instance}.salesforce.com",
168
+ "/services/async/#{api_version}/job/#{job_id}/batch",
169
+ data,
170
+ headers)
171
+ end
172
+
173
+ def self.query_batch instance, session_id, job_id, batch_id, api_version
174
+ headers = {'X-SFDC-Session' => session_id}
175
+ Http::Request.new(
176
+ :get,
177
+ "#{instance}.salesforce.com",
178
+ "/services/async/#{api_version}/job/#{job_id}/batch/#{batch_id}",
179
+ nil,
180
+ headers)
181
+ end
182
+
183
+ def self.query_batch_result_id instance, session_id, job_id, batch_id, api_version
184
+ headers = {
185
+ 'Content-Type' => 'application/xml; charset=utf-8',
186
+ 'X-SFDC-Session' => session_id}
187
+ Http::Request.new(
188
+ :get,
189
+ "#{instance}.salesforce.com",
190
+ "/services/async/#{api_version}/job/#{job_id}/batch/#{batch_id}/result",
191
+ nil,
192
+ headers)
193
+ end
194
+
195
+ def self.query_batch_result_data(instance,
196
+ session_id,
197
+ job_id,
198
+ batch_id,
199
+ result_id,
200
+ api_version)
201
+ headers = {
202
+ 'Content-Type' => 'text/csv; charset=UTF-8',
203
+ 'X-SFDC-Session' => session_id}
204
+ Http::Request.new(
205
+ :get,
206
+ "#{instance}.salesforce.com",
207
+ "/services/async/#{api_version}" \
208
+ "/job/#{job_id}/batch/#{batch_id}/result/#{result_id}",
209
+ nil,
210
+ headers)
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,3 @@
1
+ module Executrix
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,59 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Executrix::Batch do
5
+ describe '#final_status' do
6
+ it 'should return the final status if it already exists' do
7
+ b = described_class.new nil, nil, nil
8
+ expected_status = {w: :tf}
9
+ b.should_not_receive(:status)
10
+ b.instance_variable_set '@final_status', expected_status
11
+ expect(b.final_status).to eq(expected_status)
12
+ end
13
+
14
+ it 'should return specific final status for -1 batch id' do
15
+ b = described_class.new nil, nil, -1
16
+ expected_status = {
17
+ state: 'Completed',
18
+ state_message: 'Empty Request'
19
+ }
20
+ b.should_not_receive(:status)
21
+ expect(b.final_status).to eq(expected_status)
22
+ end
23
+
24
+ it 'should query the status correctly' do
25
+ b = described_class.new nil, nil, nil
26
+ b.should_receive(:status).once.and_return({w: :tf})
27
+ # TODO lookup the actual result
28
+ b.should_receive(:results).once.and_return({g: :tfo})
29
+ expect(b.final_status).to eq({w: :tf, results: {g: :tfo}})
30
+ end
31
+
32
+ it 'should yield status correctly' do
33
+ expected_running_state = {
34
+ state: 'InProgress'
35
+ }
36
+ expected_final_state = {
37
+ state: 'Completed'
38
+ }
39
+ b = described_class.new nil, nil, nil
40
+ b.should_receive(:status).once.and_return(expected_running_state)
41
+ b.should_receive(:status).once.and_return(expected_running_state)
42
+ b.should_receive(:status).once.and_return(expected_final_state)
43
+ b.should_receive(:results).once.and_return({g: :tfo})
44
+ expect{|blk| b.final_status(0, &blk)}.
45
+ to yield_successive_args(expected_running_state, expected_final_state)
46
+ end
47
+
48
+ it 'should raise exception when batch fails' do
49
+ b = described_class.new nil, nil, nil
50
+ expected_error_message = 'Generic Error Message'
51
+ b.should_receive(:status).once.and_return(
52
+ {
53
+ state: 'Failed',
54
+ state_message: expected_error_message})
55
+ expect{b.final_status}.
56
+ to raise_error(StandardError, expected_error_message)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,53 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Executrix::Connection do
5
+ let(:subject) { described_class.new nil, nil, nil, nil }
6
+
7
+ {
8
+ login: 0,
9
+ create_job: 3,
10
+ close_job: 1,
11
+ query_batch: 2,
12
+ query_batch_result_id: 2,
13
+ query_batch_result_data: 3,
14
+
15
+ }.each do |method_name, num_of_params|
16
+ describe "##{method_name}" do
17
+ it 'should delegate correctly to Http class' do
18
+ Executrix::Http.
19
+ should_receive(method_name).
20
+ and_return({})
21
+ subject.send(method_name, *Array.new(num_of_params))
22
+ end
23
+ end
24
+ end
25
+
26
+ describe '#add_query' do
27
+ it 'should delegate correctly to Http class' do
28
+ Executrix::Http.should_receive(:add_batch).
29
+ and_return({})
30
+ subject.add_query(nil, nil)
31
+ end
32
+ end
33
+
34
+ describe '#add_batch' do
35
+ it 'should delegate correctly to underlying classes' do
36
+ Executrix::Http.should_receive(:add_batch).
37
+ and_return({})
38
+ Executrix::Helper.should_receive(:records_to_csv).
39
+ and_return('My,Awesome,CSV')
40
+ subject.add_batch(nil, 'non emtpy records')
41
+ end
42
+
43
+ it 'should return -1 for nil input' do
44
+ return_code = subject.add_batch(nil, nil)
45
+ expect(return_code).to eq(-1)
46
+ end
47
+
48
+ it 'should return -1 for empty input' do
49
+ return_code = subject.add_batch(nil, [])
50
+ expect(return_code).to eq(-1)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,64 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Executrix::Helper do
5
+ describe '.records_to_csv' do
6
+ it 'should return valid csv for single record' do
7
+ input = [
8
+ {'Title' => 'Awesome Title', 'Name' => 'A name'},
9
+ ]
10
+
11
+ expected_csv = "\"Title\",\"Name\"\n" \
12
+ "\"Awesome Title\",\"A name\"\n"
13
+ expect(described_class.records_to_csv(input)).to eq(expected_csv)
14
+ end
15
+
16
+ it 'should return valid csv for basic records' do
17
+ input = [
18
+ {'Title' => 'Awesome Title', 'Name' => 'A name'},
19
+ {'Title' => 'A second Title', 'Name' => 'A second name'},
20
+ ]
21
+
22
+ expected_csv = "\"Title\",\"Name\"\n" \
23
+ "\"Awesome Title\",\"A name\"\n" \
24
+ "\"A second Title\",\"A second name\"\n"
25
+ expect(described_class.records_to_csv(input)).to eq(expected_csv)
26
+ end
27
+
28
+ it 'should return valid csv when first row misses a key' do
29
+ input = [
30
+ {'Title' => 'Awesome Title', 'Name' => 'A name'},
31
+ {'Title' => 'A second Title', 'Name' => 'A second name', 'Something' => 'Else'},
32
+ ]
33
+
34
+ expected_csv = "\"Title\",\"Name\",\"Something\"\n" \
35
+ "\"Awesome Title\",\"A name\",\"\"\n" \
36
+ "\"A second Title\",\"A second name\",\"Else\"\n"
37
+ expect(described_class.records_to_csv(input)).to eq(expected_csv)
38
+ end
39
+
40
+ it 'should correctly convert Array to Multi-Picklist' do
41
+ input = [
42
+ {'Title' => 'Awesome Title', 'Picklist' => ['Several', 'Values']},
43
+ {'Title' => 'A second Title', 'Picklist' => ['SingleValue']},
44
+ ]
45
+
46
+ expected_csv = "\"Title\",\"Picklist\"\n" \
47
+ "\"Awesome Title\",\"Several;Values\"\n" \
48
+ "\"A second Title\",\"SingleValue\"\n"
49
+ expect(described_class.records_to_csv(input)).to eq(expected_csv)
50
+ end
51
+
52
+ it 'should return valid csv when order of keys varies' do
53
+ input = [
54
+ {'Title' => 'Awesome Title', 'Name' => 'A name'},
55
+ {'Name' => 'A second name', 'Title' => 'A second Title'},
56
+ ]
57
+
58
+ expected_csv = "\"Title\",\"Name\"\n" \
59
+ "\"Awesome Title\",\"A name\"\n" \
60
+ "\"A second Title\",\"A second name\"\n"
61
+ expect(described_class.records_to_csv(input)).to eq(expected_csv)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,338 @@
1
+ #encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Executrix::Http do
5
+ describe '#process_http_request' do
6
+ let(:post_request) do
7
+ Executrix::Http::Request.new(
8
+ :post,
9
+ 'test.host',
10
+ '/',
11
+ 'post body',
12
+ {'X-SFDC-Session' => 'super_secret'})
13
+ end
14
+
15
+ let(:get_request) do
16
+ Executrix::Http::Request.new(:get, 'test.host', '/', '', [])
17
+ end
18
+
19
+ it 'should return a response object' do
20
+ expected_body = 'correct result'
21
+ stub_request(:post, 'https://test.host').
22
+ with(
23
+ body: post_request.body,
24
+ headers: post_request.headers).
25
+ to_return(:body => expected_body)
26
+ res = Executrix::Http.process_http_request(post_request)
27
+ expect(res).to eq(expected_body)
28
+ end
29
+ end
30
+
31
+ describe '#create_login' do
32
+ let(:login_error_message) do
33
+ 'INVALID_LOGIN: Invalid username, password, security token;' \
34
+ 'or user locked out.'
35
+ end
36
+
37
+ let(:sf_instance) {'cs7'}
38
+
39
+ let(:login_success) do
40
+ %Q{<?xml version="1.0" encoding="UTF-8"?>
41
+ <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
42
+ <soapenv:Body>
43
+ <loginResponse>
44
+ <result>
45
+ <metadataServerUrl>https://#{sf_instance}-api.salesforce.com/services/Soap/m/27.0/00EU00000095Y5b</metadataServerUrl>
46
+ <passwordExpired>false</passwordExpired>
47
+ <sandbox>true</sandbox>
48
+ <serverUrl>https://#{sf_instance}-api.salesforce.com/services/Soap/u/27.0/00EU00000095Y5b</serverUrl>
49
+ <sessionId>00DM00000099X9a!AQ7AQJ9BjrYfF2h_G9_VERsCRVjTMAPaWjz2zuqqRBduYvKCmHexVbKdtxFMrgTnV9Xi.M80AhIkjnwuEVxI00ChG09._Q_X</sessionId>
50
+ <userId>005K001100243YPIAY</userId>
51
+ <userInfo>
52
+ <accessibilityMode>false</accessibilityMode>
53
+ <currencySymbol xsi:nil="true"/>
54
+ <orgAttachmentFileSizeLimit>2621440</orgAttachmentFileSizeLimit>
55
+ <orgDefaultCurrencyIsoCode xsi:nil="true"/>
56
+ <orgDisallowHtmlAttachments>false</orgDisallowHtmlAttachments>
57
+ <orgHasPersonAccounts>false</orgHasPersonAccounts>
58
+ <organizationId>00ID0000OrgFoo</organizationId>
59
+ <organizationMultiCurrency>true</organizationMultiCurrency>
60
+ <organizationName>Hinz &amp; Kunz</organizationName>
61
+ <profileId>00eA0000000nJEY</profileId>
62
+ <roleId xsi:nil="true"/>
63
+ <sessionSecondsValid>3600</sessionSecondsValid>
64
+ <userDefaultCurrencyIsoCode>EUR</userDefaultCurrencyIsoCode>
65
+ <userEmail>theadmin@example.com</userEmail>
66
+ <userFullName>John Doe</userFullName>
67
+ <userId>005D0000002b3SPIAY</userId>
68
+ <userLanguage>de</userLanguage>
69
+ <userLocale>de_DE_EURO</userLocale>
70
+ <userName>theadmin@exammple.com.euvconfig</userName>
71
+ <userTimeZone>Europe/Berlin</userTimeZone>
72
+ <userType>Standard</userType>
73
+ <userUiSkin>Theme42</userUiSkin>
74
+ </userInfo>
75
+ </result>
76
+ </loginResponse>
77
+ </soapenv:Body>
78
+ </soapenv:Envelope>}
79
+ end
80
+
81
+ let(:login_success_new) do
82
+ %Q{<?xml version="1.0" encoding="UTF-8"?>
83
+ <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
84
+ <soapenv:Body>
85
+ <loginResponse>
86
+ <result>
87
+ <metadataServerUrl>https://#{sf_instance}.salesforce.com/services/Soap/m/27.0/00EU00000095Y5b</metadataServerUrl>
88
+ <passwordExpired>false</passwordExpired>
89
+ <sandbox>true</sandbox>
90
+ <serverUrl>https://#{sf_instance}.salesforce.com/services/Soap/u/27.0/00EU00000095Y5b</serverUrl>
91
+ <sessionId>00DM00000099X9a!AQ7AQJ9BjrYfF2h_G9_VERsCRVjTMAPaWjz2zuqqRBduYvKCmHexVbKdtxFMrgTnV9Xi.M80AhIkjnwuEVxI00ChG09._Q_X</sessionId>
92
+ <userId>005K001100243YPIAY</userId>
93
+ <userInfo>
94
+ <accessibilityMode>false</accessibilityMode>
95
+ <currencySymbol xsi:nil="true"/>
96
+ <orgAttachmentFileSizeLimit>2621440</orgAttachmentFileSizeLimit>
97
+ <orgDefaultCurrencyIsoCode xsi:nil="true"/>
98
+ <orgDisallowHtmlAttachments>false</orgDisallowHtmlAttachments>
99
+ <orgHasPersonAccounts>false</orgHasPersonAccounts>
100
+ <organizationId>00ID0000OrgFoo</organizationId>
101
+ <organizationMultiCurrency>true</organizationMultiCurrency>
102
+ <organizationName>Hinz &amp; Kunz</organizationName>
103
+ <profileId>00eA0000000nJEY</profileId>
104
+ <roleId xsi:nil="true"/>
105
+ <sessionSecondsValid>3600</sessionSecondsValid>
106
+ <userDefaultCurrencyIsoCode>EUR</userDefaultCurrencyIsoCode>
107
+ <userEmail>theadmin@example.com</userEmail>
108
+ <userFullName>John Doe</userFullName>
109
+ <userId>005D0000002b3SPIAY</userId>
110
+ <userLanguage>de</userLanguage>
111
+ <userLocale>de_DE_EURO</userLocale>
112
+ <userName>theadmin@exammple.com.euvconfig</userName>
113
+ <userTimeZone>Europe/Berlin</userTimeZone>
114
+ <userType>Standard</userType>
115
+ <userUiSkin>Theme42</userUiSkin>
116
+ </userInfo>
117
+ </result>
118
+ </loginResponse>
119
+ </soapenv:Body>
120
+ </soapenv:Envelope>}
121
+ end
122
+
123
+ let(:login_error) do
124
+ %Q{<?xml version="1.0" encoding="UTF-8"?>
125
+ <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
126
+ xmlns:sf="urn:fault.partner.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
127
+ <soapenv:Body>
128
+ <soapenv:Fault>
129
+ <faultcode>INVALID_LOGIN</faultcode>
130
+ <faultstring>#{login_error_message}</faultstring>
131
+ <detail>
132
+ <sf:LoginFault xsi:type="sf:LoginFault">
133
+ <sf:exceptionCode>INVALID_LOGIN</sf:exceptionCode>
134
+ <sf:exceptionMessage>Invalid username, password, │Your bundle is complete!security token; or user locked out.
135
+ </sf:exceptionMessage>
136
+ </sf:LoginFault>
137
+ </detail>
138
+ </soapenv:Fault>
139
+ </soapenv:Body>
140
+ </soapenv:Envelope>}
141
+ end
142
+
143
+ it 'should raise an error for faulty login' do
144
+ Executrix::Http.should_receive(:process_http_request).
145
+ and_return(login_error)
146
+ expect{ Executrix::Http.login('a','b','c', 'd') }.
147
+ to raise_error(RuntimeError, login_error_message)
148
+ end
149
+
150
+ it 'should return hash for correct login' do
151
+ [login_success, login_success_new].each do |login_response|
152
+ Executrix::Http.should_receive(:process_http_request).
153
+ and_return(login_response)
154
+ result = Executrix::Http.login('a','b','c', 'd')
155
+ expect(result).to be_a(Hash)
156
+ expect(result).to have_key(:session_id)
157
+ expect(result).to have_key(:server_url)
158
+ expect(result[:instance]).to eq(sf_instance)
159
+ end
160
+ end
161
+ end
162
+
163
+ describe '#create_job' do
164
+ let(:create_job_success) do
165
+ %Q{<?xml version="1.0" encoding="UTF-8"?>
166
+ <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
167
+ <id>750D0000000002lIAA</id>
168
+ <operation>upsert</operation>
169
+ <object>Contact</object>
170
+ <createdById>005D0000001ALVFIA4</createdById>
171
+ <createdDate>2013-04-10T15:52:02.000Z</createdDate>
172
+ <systemModstamp>2013-04-10T15:52:02.000Z</systemModstamp>
173
+ <state>Open</state>
174
+ <externalIdFieldName>my_external_field__c</externalIdFieldName>
175
+ <concurrencyMode>Parallel</concurrencyMode>
176
+ <contentType>CSV</contentType>
177
+ <numberBatchesQueued>0</numberBatchesQueued>
178
+ <numberBatchesInProgress>0</numberBatchesInProgress>
179
+ <numberBatchesCompleted>0</numberBatchesCompleted>
180
+ <numberBatchesFailed>0</numberBatchesFailed>
181
+ <numberBatchesTotal>0</numberBatchesTotal>
182
+ <numberRecordsProcessed>0</numberRecordsProcessed>
183
+ <numberRetries>0</numberRetries>
184
+ <apiVersion>27.0</apiVersion>
185
+ <numberRecordsFailed>0</numberRecordsFailed>
186
+ <totalProcessingTime>0</totalProcessingTime>
187
+ <apiActiveProcessingTime>0</apiActiveProcessingTime>
188
+ <apexProcessingTime>0</apexProcessingTime>
189
+ </jobInfo>}
190
+ end
191
+
192
+ it 'should return hash for creating job' do
193
+ Executrix::Http.should_receive(:process_http_request).
194
+ and_return(create_job_success)
195
+ result = Executrix::Http.create_job('a','b','c','d', 'e')
196
+ expect(result).to be_a(Hash)
197
+ expect(result).to have_key(:id)
198
+ expect(result).to have_key(:operation)
199
+ end
200
+ end
201
+
202
+ describe '#add_batch' do
203
+ let(:add_batch_success) do
204
+ %Q{
205
+ <?xml version="1.0" encoding="UTF-8"?>
206
+ <batchInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
207
+ <id>750M0000000B1Z6IAL</id>
208
+ <jobId>751K00000009x71IAA</jobId>
209
+ <state>Queued</state>
210
+ <createdDate>2013-04-10T15:53:46.000Z</createdDate>
211
+ <systemModstamp>2013-04-10T15:53:46.000Z</systemModstamp>
212
+ <numberRecordsProcessed>0</numberRecordsProcessed>
213
+ <numberRecordsFailed>0</numberRecordsFailed>
214
+ <totalProcessingTime>0</totalProcessingTime>
215
+ <apiActiveProcessingTime>0</apiActiveProcessingTime>
216
+ <apexProcessingTime>0</apexProcessingTime>
217
+ </batchInfo>
218
+ }
219
+ end
220
+
221
+ it 'should return hash for adding batch' do
222
+ Executrix::Http.should_receive(:process_http_request).
223
+ and_return(add_batch_success)
224
+ result = Executrix::Http.add_batch(:post,'a','b','c','d')
225
+ expect(result).to be_a(Hash)
226
+ expect(result).to have_key(:id)
227
+ expect(result).to have_key(:job_id)
228
+ expect(result).to have_key(:state)
229
+ end
230
+ end
231
+
232
+ describe '#close_job' do
233
+ let(:close_job_success) do
234
+ %Q{<?xml version="1.0" encoding="UTF-8"?>
235
+ <jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
236
+ <id>750D0000000002jIAA</id>
237
+ <operation>upsert</operation>
238
+ <object>Contact</object>
239
+ <createdById>005D0000002b3SPIAY</createdById>
240
+ <createdDate>2013-04-10T16:27:56.000Z</createdDate>
241
+ <systemModstamp>2013-04-10T16:27:56.000Z</systemModstamp>
242
+ <state>Closed</state>
243
+ <externalIdFieldName>my_external_id__c</externalIdFieldName>
244
+ <concurrencyMode>Parallel</concurrencyMode>
245
+ <contentType>CSV</contentType>
246
+ <numberBatchesQueued>0</numberBatchesQueued>
247
+ <numberBatchesInProgress>0</numberBatchesInProgress>
248
+ <numberBatchesCompleted>1</numberBatchesCompleted>
249
+ <numberBatchesFailed>0</numberBatchesFailed>
250
+ <numberBatchesTotal>1</numberBatchesTotal>
251
+ <numberRecordsProcessed>4</numberRecordsProcessed>
252
+ <numberRetries>0</numberRetries>
253
+ <apiVersion>27.0</apiVersion>
254
+ <numberRecordsFailed>0</numberRecordsFailed>
255
+ <totalProcessingTime>838</totalProcessingTime>
256
+ <apiActiveProcessingTime>355</apiActiveProcessingTime>
257
+ <apexProcessingTime>253</apexProcessingTime>
258
+ </jobInfo>}
259
+ end
260
+
261
+ it 'should return hash for closing job' do
262
+ Executrix::Http.should_receive(:process_http_request).
263
+ and_return(close_job_success)
264
+ result = Executrix::Http.close_job('a','b','c','d')
265
+ expect(result).to be_a(Hash)
266
+ expect(result).to have_key(:id)
267
+ expect(result).to have_key(:object)
268
+ expect(result).to have_key(:operation)
269
+ expect(result).to have_key(:state)
270
+ expect(result).to have_key(:content_type)
271
+ end
272
+ end
273
+
274
+ describe '#add_batch' do
275
+ let(:invalid_session_id) do
276
+ %Q{<?xml version="1.0" encoding="UTF-8"?>
277
+ <error xmlns="http://www.force.com/2009/06/asyncapi/dataload">
278
+ <exceptionCode>InvalidSessionId</exceptionCode>
279
+ <exceptionMessage>Invalid session id</exceptionMessage>
280
+ </error>}
281
+ end
282
+
283
+ it 'should raise an exception on faulty authorization' do
284
+ Executrix::Http.should_receive(:process_http_request).
285
+ and_return(invalid_session_id)
286
+ expect{Executrix::Http.query_batch('a','b','c','d','e')}.
287
+ to raise_error(RuntimeError, 'InvalidSessionId: Invalid session id')
288
+ end
289
+ end
290
+
291
+ describe '#query_batch_result_id' do
292
+ let(:batch_result_success) do
293
+ %Q{<result-list xmlns="http://www.force.com/2009/06/asyncapi/dataload">
294
+ <result>750D0000002jIAA</result>
295
+ </result-list>}
296
+ end
297
+
298
+ it 'should return hash including the result id' do
299
+ Executrix::Http.should_receive(:process_http_request).
300
+ and_return(batch_result_success)
301
+ result = Executrix::Http.query_batch_result_id('a','b','c','d','e')
302
+ expect(result).to be_a(Hash)
303
+ expect(result).to have_key(:result)
304
+ end
305
+ end
306
+
307
+ describe '#query_batch_result_data' do
308
+ let(:batch_result_data_success) do
309
+ %Q{"Id","my_external_id__c"
310
+ "003M000057GH39aIAD","K-00J799"
311
+ "003M001200KO82cIAD","K-015699"}
312
+ end
313
+
314
+ let(:batch_result_data_with_spaces_success) do
315
+ %Q{"Id","Name"
316
+ "003K000057GH39aIAD","Master of Disaster"
317
+ "003K001200KO82cIAD","King of the Hill"}
318
+ end
319
+
320
+ it 'should return array of arrays for data' do
321
+ Executrix::Http.should_receive(:process_http_request).
322
+ and_return(batch_result_data_success)
323
+ result = Executrix::Http.query_batch_result_data('a','b','c','d','e','f')
324
+ expect(result).to eq([
325
+ {'Id' => '003M000057GH39aIAD', 'my_external_id__c' => 'K-00J799'},
326
+ {'Id' => '003M001200KO82cIAD', 'my_external_id__c' => 'K-015699'}])
327
+ end
328
+
329
+ it 'should return correct array with spaces' do
330
+ Executrix::Http.should_receive(:process_http_request).
331
+ and_return(batch_result_data_with_spaces_success)
332
+ result = Executrix::Http.query_batch_result_data('a','b','c','d','e','f')
333
+ expect(result).to eq([
334
+ {'Id' => '003K000057GH39aIAD', 'Name' => 'Master of Disaster'},
335
+ {'Id' => '003K001200KO82cIAD', 'Name' => 'King of the Hill'}])
336
+ end
337
+ end
338
+ end