executrix 1.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.
@@ -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