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.
- 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
|