executrix 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/Gemfile +13 -0
- data/Guardfile +8 -0
- data/LICENSE +9 -0
- data/README.md +120 -0
- data/Rakefile +6 -0
- data/executrix.gemspec +27 -0
- data/lib/executrix.rb +58 -0
- data/lib/executrix/batch.rb +51 -0
- data/lib/executrix/connection.rb +95 -0
- data/lib/executrix/helper.rb +36 -0
- data/lib/executrix/http.rb +214 -0
- data/lib/executrix/version.rb +3 -0
- data/spec/lib/executrix/batch_spec.rb +59 -0
- data/spec/lib/executrix/connection_spec.rb +53 -0
- data/spec/lib/executrix/helper_spec.rb +64 -0
- data/spec/lib/executrix/http_spec.rb +338 -0
- data/spec/lib/executrix_spec.rb +64 -0
- data/spec/spec_helper.rb +16 -0
- metadata +142 -0
@@ -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,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 & 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 & 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
|