executrix 1.1.3 → 1.2.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.
- data/README.md +10 -0
- data/executrix.gemspec +4 -3
- data/lib/executrix/connection.rb +11 -1
- data/lib/executrix/helper.rb +28 -0
- data/lib/executrix/http.rb +16 -3
- data/lib/executrix/version.rb +1 -1
- data/lib/executrix.rb +36 -1
- data/spec/lib/executrix/batch_spec.rb +14 -14
- data/spec/lib/executrix/connection_spec.rb +16 -16
- data/spec/lib/executrix/helper_spec.rb +135 -8
- data/spec/lib/executrix/http_spec.rb +20 -20
- data/spec/lib/executrix_spec.rb +70 -27
- metadata +63 -48
data/README.md
CHANGED
@@ -86,6 +86,16 @@ res = salesforce.query('Account', 'select id, name, createddate from Account lim
|
|
86
86
|
puts res.result.records.inspect
|
87
87
|
~~~
|
88
88
|
|
89
|
+
## File Upload
|
90
|
+
|
91
|
+
For file uploads, just add a `File` object to the binary columns.
|
92
|
+
~~~ ruby
|
93
|
+
attachment = {'ParentId' => '00Kk0001908kqkDEAQ', 'Name' => 'attachment.pdf', 'Body' => File.new('tmp/attachment.pdf')}
|
94
|
+
records_to_insert = []
|
95
|
+
records_to_insert << attachment
|
96
|
+
salesforce.insert('Attachment', records_to_insert)
|
97
|
+
~~~
|
98
|
+
|
89
99
|
### Query status
|
90
100
|
|
91
101
|
The above examples all return immediately after sending the data to the Bulk API. If you want to wait, until the batch finished, call the final_status method on the batch-reference.
|
data/executrix.gemspec
CHANGED
@@ -20,8 +20,9 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.require_paths = ["lib"]
|
21
21
|
|
22
22
|
gem.add_dependency 'rake'
|
23
|
-
gem.add_dependency 'nori', '< 2.
|
23
|
+
gem.add_dependency 'nori', '< 2.4'
|
24
24
|
gem.add_dependency 'nokogiri', '< 1.7'
|
25
|
-
gem.
|
26
|
-
gem.add_development_dependency '
|
25
|
+
gem.add_dependency 'rubyzip', '< 1.1'
|
26
|
+
gem.add_development_dependency 'rspec', '< 2.15'
|
27
|
+
gem.add_development_dependency 'webmock', '< 1.14'
|
27
28
|
end
|
data/lib/executrix/connection.rb
CHANGED
@@ -24,12 +24,13 @@ module Executrix
|
|
24
24
|
@session_id.split('!').first
|
25
25
|
end
|
26
26
|
|
27
|
-
def create_job operation, sobject, external_field
|
27
|
+
def create_job operation, sobject, content_type, external_field
|
28
28
|
Executrix::Http.create_job(
|
29
29
|
@instance,
|
30
30
|
@session_id,
|
31
31
|
operation,
|
32
32
|
sobject,
|
33
|
+
content_type,
|
33
34
|
@api_version,
|
34
35
|
external_field)[:id]
|
35
36
|
end
|
@@ -82,6 +83,15 @@ module Executrix
|
|
82
83
|
)
|
83
84
|
end
|
84
85
|
|
86
|
+
def add_file_upload_batch job_id, filename
|
87
|
+
Executrix::Http.add_file_upload_batch(
|
88
|
+
@instance,
|
89
|
+
@session_id,
|
90
|
+
job_id,
|
91
|
+
filename,
|
92
|
+
@api_version)[:id]
|
93
|
+
end
|
94
|
+
|
85
95
|
def add_batch job_id, records
|
86
96
|
return -1 if records.nil? || records.empty?
|
87
97
|
|
data/lib/executrix/helper.rb
CHANGED
@@ -37,5 +37,33 @@ module Executrix
|
|
37
37
|
before_sf = server_url[/^https?:\/\/(.+)\.salesforce\.com/, 1]
|
38
38
|
before_sf.gsub(/-api$/,'')
|
39
39
|
end
|
40
|
+
|
41
|
+
def attachment_keys records
|
42
|
+
records.map do |record|
|
43
|
+
record.select do |key, value|
|
44
|
+
value.class == File
|
45
|
+
end.keys
|
46
|
+
end.flatten.uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
def transform_values! records, keys
|
50
|
+
keys.each do |key|
|
51
|
+
records.each do |record|
|
52
|
+
file_handle = record[key]
|
53
|
+
if file_handle
|
54
|
+
file_path = File.absolute_path(file_handle)
|
55
|
+
record
|
56
|
+
.merge!({
|
57
|
+
key => Executrix::Helper.absolute_to_relative_path(file_path,'#')
|
58
|
+
})
|
59
|
+
yield file_path if block_given?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def absolute_to_relative_path input, replacement
|
66
|
+
input.gsub(/(^C:[\/\\])|(^\/)/,replacement)
|
67
|
+
end
|
40
68
|
end
|
41
69
|
end
|
data/lib/executrix/http.rb
CHANGED
@@ -41,6 +41,20 @@ module Executrix
|
|
41
41
|
process_csv_response(process_http_request(r))
|
42
42
|
end
|
43
43
|
|
44
|
+
def add_file_upload_batch instance, session_id, job_id, filename, api_version
|
45
|
+
data = File.read(filename)
|
46
|
+
headers = {
|
47
|
+
'Content-Type' => 'zip/csv',
|
48
|
+
'X-SFDC-Session' => session_id}
|
49
|
+
r = Http::Request.new(
|
50
|
+
:post,
|
51
|
+
Http::Request.generic_host(instance),
|
52
|
+
"/services/async/#{api_version}/job/#{job_id}/batch",
|
53
|
+
data,
|
54
|
+
headers)
|
55
|
+
process_xml_response(nori.parse(process_http_request(r)))
|
56
|
+
end
|
57
|
+
|
44
58
|
def process_http_request(r)
|
45
59
|
http = Net::HTTP.new(r.host, 443)
|
46
60
|
http.use_ssl = true
|
@@ -120,7 +134,7 @@ module Executrix
|
|
120
134
|
headers)
|
121
135
|
end
|
122
136
|
|
123
|
-
def self.create_job instance, session_id, operation, sobject, api_version, external_field = nil
|
137
|
+
def self.create_job instance, session_id, operation, sobject, content_type, api_version, external_field = nil
|
124
138
|
external_field_line = external_field ?
|
125
139
|
"<externalIdFieldName>#{external_field}</externalIdFieldName>" : nil
|
126
140
|
body = %Q{<?xml version="1.0" encoding="utf-8" ?>
|
@@ -128,7 +142,7 @@ module Executrix
|
|
128
142
|
<operation>#{operation}</operation>
|
129
143
|
<object>#{sobject}</object>
|
130
144
|
#{external_field_line}
|
131
|
-
<contentType
|
145
|
+
<contentType>#{content_type}</contentType>
|
132
146
|
</jobInfo>
|
133
147
|
}
|
134
148
|
headers = {
|
@@ -209,7 +223,6 @@ module Executrix
|
|
209
223
|
headers)
|
210
224
|
end
|
211
225
|
|
212
|
-
private
|
213
226
|
def self.generic_host prefix
|
214
227
|
"#{prefix}.salesforce.com"
|
215
228
|
end
|
data/lib/executrix/version.rb
CHANGED
data/lib/executrix.rb
CHANGED
@@ -3,6 +3,7 @@ require 'executrix/helper'
|
|
3
3
|
require 'executrix/batch'
|
4
4
|
require 'executrix/http'
|
5
5
|
require 'executrix/connection'
|
6
|
+
require 'zip'
|
6
7
|
|
7
8
|
module Executrix
|
8
9
|
class Api
|
@@ -40,6 +41,7 @@ module Executrix
|
|
40
41
|
job_id = @connection.create_job(
|
41
42
|
'query',
|
42
43
|
sobject,
|
44
|
+
'CSV',
|
43
45
|
nil)
|
44
46
|
batch_id = @connection.add_query(job_id, query)
|
45
47
|
@connection.close_job job_id
|
@@ -49,11 +51,44 @@ module Executrix
|
|
49
51
|
|
50
52
|
private
|
51
53
|
def start_job(operation, sobject, records, external_field=nil)
|
54
|
+
attachment_keys = Executrix::Helper.attachment_keys(records)
|
55
|
+
|
56
|
+
content_type = 'CSV'
|
57
|
+
zip_filename = nil
|
58
|
+
request_filename = nil
|
59
|
+
batch_id = -1
|
60
|
+
if not attachment_keys.empty?
|
61
|
+
tmp_filename = Dir::Tmpname.make_tmpname('bulk_upload', '.zip')
|
62
|
+
zip_filename = "#{Dir.tmpdir}/#{tmp_filename}"
|
63
|
+
Zip::File.open(zip_filename, Zip::File::CREATE) do |zipfile|
|
64
|
+
Executrix::Helper.transform_values!(records, attachment_keys) do |path|
|
65
|
+
zipfile.add(Executrix::Helper.absolute_to_relative_path(path, ''), path)
|
66
|
+
end
|
67
|
+
tmp_filename = Dir::Tmpname.make_tmpname('request', '.txt')
|
68
|
+
request_filename = "#{Dir.tmpdir}/#{tmp_filename}"
|
69
|
+
File.open(request_filename, 'w') do |file|
|
70
|
+
file.write(Executrix::Helper.records_to_csv(records))
|
71
|
+
end
|
72
|
+
zipfile.add('request.txt', request_filename)
|
73
|
+
end
|
74
|
+
|
75
|
+
content_type = 'ZIP_CSV'
|
76
|
+
end
|
77
|
+
|
52
78
|
job_id = @connection.create_job(
|
53
79
|
operation,
|
54
80
|
sobject,
|
81
|
+
content_type,
|
55
82
|
external_field)
|
56
|
-
|
83
|
+
if zip_filename
|
84
|
+
batch_id = @connection.add_file_upload_batch job_id, zip_filename
|
85
|
+
[zip_filename, request_filename].each do |file|
|
86
|
+
File.delete(file) if file
|
87
|
+
end
|
88
|
+
else
|
89
|
+
batch_id = @connection.add_batch job_id, records
|
90
|
+
end
|
91
|
+
|
57
92
|
@connection.close_job job_id
|
58
93
|
Executrix::Batch.new @connection, job_id, batch_id
|
59
94
|
end
|
@@ -3,33 +3,33 @@ require 'spec_helper'
|
|
3
3
|
|
4
4
|
describe Executrix::Batch do
|
5
5
|
describe '#final_status' do
|
6
|
-
it '
|
6
|
+
it 'returns the final status if it already exists' do
|
7
7
|
b = described_class.new nil, nil, nil
|
8
8
|
expected_status = {w: :tf}
|
9
|
-
b.
|
9
|
+
expect(b).not_to receive(:status)
|
10
10
|
b.instance_variable_set '@final_status', expected_status
|
11
11
|
expect(b.final_status).to eq(expected_status)
|
12
12
|
end
|
13
13
|
|
14
|
-
it '
|
14
|
+
it 'returns specific final status for -1 batch id' do
|
15
15
|
b = described_class.new nil, nil, -1
|
16
16
|
expected_status = {
|
17
17
|
state: 'Completed',
|
18
18
|
state_message: 'Empty Request',
|
19
19
|
}
|
20
|
-
b.
|
20
|
+
expect(b).not_to receive(:status)
|
21
21
|
expect(b.final_status).to eq(expected_status)
|
22
22
|
end
|
23
23
|
|
24
|
-
it '
|
24
|
+
it 'queries the status correctly' do
|
25
25
|
b = described_class.new nil, nil, nil
|
26
|
-
b.
|
26
|
+
expect(b).to receive(:status).once.and_return({w: :tf})
|
27
27
|
# TODO lookup the actual result
|
28
|
-
b.
|
28
|
+
expect(b).to receive(:results).once.and_return({g: :tfo})
|
29
29
|
expect(b.final_status).to eq({w: :tf, results: {g: :tfo}})
|
30
30
|
end
|
31
31
|
|
32
|
-
it '
|
32
|
+
it 'yields status correctly' do
|
33
33
|
expected_running_state = {
|
34
34
|
state: 'InProgress'
|
35
35
|
}
|
@@ -37,18 +37,18 @@ describe Executrix::Batch do
|
|
37
37
|
state: 'Completed'
|
38
38
|
}
|
39
39
|
b = described_class.new nil, nil, nil
|
40
|
-
b.
|
41
|
-
b.
|
42
|
-
b.
|
43
|
-
b.
|
40
|
+
expect(b).to receive(:status).once.and_return(expected_running_state)
|
41
|
+
expect(b).to receive(:status).once.and_return(expected_running_state)
|
42
|
+
expect(b).to receive(:status).once.and_return(expected_final_state)
|
43
|
+
expect(b).to receive(:results).once.and_return({g: :tfo})
|
44
44
|
expect{|blk| b.final_status(0, &blk)}
|
45
45
|
.to yield_successive_args(expected_running_state, expected_final_state)
|
46
46
|
end
|
47
47
|
|
48
|
-
it '
|
48
|
+
it 'raises exception when batch fails' do
|
49
49
|
b = described_class.new nil, nil, nil
|
50
50
|
expected_error_message = 'Generic Error Message'
|
51
|
-
b.
|
51
|
+
expect(b).to receive(:status).once.and_return(
|
52
52
|
{
|
53
53
|
state: 'Failed',
|
54
54
|
state_message: expected_error_message})
|
@@ -6,16 +6,16 @@ describe Executrix::Connection do
|
|
6
6
|
|
7
7
|
{
|
8
8
|
login: 0,
|
9
|
-
create_job:
|
9
|
+
create_job: 4,
|
10
10
|
close_job: 1,
|
11
11
|
query_batch: 2,
|
12
12
|
query_batch_result_id: 2,
|
13
13
|
query_batch_result_data: 3,
|
14
14
|
}.each do |method_name, num_of_params|
|
15
15
|
describe "##{method_name}" do
|
16
|
-
it '
|
17
|
-
Executrix::Http
|
18
|
-
.
|
16
|
+
it 'delegates correctly to Http class' do
|
17
|
+
expect(Executrix::Http)
|
18
|
+
.to receive(method_name)
|
19
19
|
.and_return({})
|
20
20
|
subject.send(method_name, *Array.new(num_of_params))
|
21
21
|
end
|
@@ -23,22 +23,22 @@ describe Executrix::Connection do
|
|
23
23
|
end
|
24
24
|
|
25
25
|
describe '#add_query' do
|
26
|
-
it '
|
27
|
-
Executrix::Http.
|
26
|
+
it 'delegates correctly to Http class' do
|
27
|
+
expect(Executrix::Http).to receive(:add_batch)
|
28
28
|
.and_return({})
|
29
29
|
subject.add_query(nil, nil)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
describe '#org_id' do
|
34
|
-
it '
|
34
|
+
it 'raises exception when not logged in' do
|
35
35
|
expect {subject.org_id}.to raise_error(RuntimeError)
|
36
36
|
end
|
37
37
|
|
38
|
-
it '
|
38
|
+
it 'returns correct OrgId after login' do
|
39
39
|
org_id = '00D50000000IehZ'
|
40
|
-
Executrix::Http
|
41
|
-
.
|
40
|
+
expect(Executrix::Http)
|
41
|
+
.to receive(:login)
|
42
42
|
.and_return({session_id: "#{org_id}!AQcAQH0dMHZfz972Szmpkb58urFRkgeBGsxL_QJWwYMfAbUeeG7c1E6LYUfiDUkWe6H34r1AAwOR8B8fLEz6n04NPGRrq0FM"})
|
43
43
|
expect(subject.login.org_id).to eq(org_id)
|
44
44
|
end
|
@@ -46,20 +46,20 @@ describe Executrix::Connection do
|
|
46
46
|
|
47
47
|
|
48
48
|
describe '#add_batch' do
|
49
|
-
it '
|
50
|
-
Executrix::Http.
|
49
|
+
it 'delegates correctly to underlying classes' do
|
50
|
+
expect(Executrix::Http).to receive(:add_batch)
|
51
51
|
.and_return({})
|
52
|
-
Executrix::Helper.
|
52
|
+
expect(Executrix::Helper).to receive(:records_to_csv)
|
53
53
|
.and_return('My,Awesome,CSV')
|
54
|
-
subject.add_batch(nil, '
|
54
|
+
subject.add_batch(nil, [{'non_emtpy' => 'records'}])
|
55
55
|
end
|
56
56
|
|
57
|
-
it '
|
57
|
+
it 'returns -1 for nil input' do
|
58
58
|
return_code = subject.add_batch(nil, nil)
|
59
59
|
expect(return_code).to eq(-1)
|
60
60
|
end
|
61
61
|
|
62
|
-
it '
|
62
|
+
it 'returns -1 for empty input' do
|
63
63
|
return_code = subject.add_batch(nil, [])
|
64
64
|
expect(return_code).to eq(-1)
|
65
65
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
|
4
4
|
describe Executrix::Helper do
|
5
5
|
describe '.records_to_csv' do
|
6
|
-
it '
|
6
|
+
it 'returns valid csv for single record' do
|
7
7
|
input = [
|
8
8
|
{'Title' => 'Awesome Title', 'Name' => 'A name'},
|
9
9
|
]
|
@@ -13,7 +13,7 @@ describe Executrix::Helper do
|
|
13
13
|
expect(described_class.records_to_csv(input)).to eq(expected_csv)
|
14
14
|
end
|
15
15
|
|
16
|
-
it '
|
16
|
+
it 'returns valid csv for basic records' do
|
17
17
|
input = [
|
18
18
|
{'Title' => 'Awesome Title', 'Name' => 'A name'},
|
19
19
|
{'Title' => 'A second Title', 'Name' => 'A second name'},
|
@@ -25,7 +25,7 @@ describe Executrix::Helper do
|
|
25
25
|
expect(described_class.records_to_csv(input)).to eq(expected_csv)
|
26
26
|
end
|
27
27
|
|
28
|
-
it '
|
28
|
+
it 'returns valid csv when first row misses a key' do
|
29
29
|
input = [
|
30
30
|
{'Title' => 'Awesome Title', 'Name' => 'A name'},
|
31
31
|
{'Title' => 'A second Title', 'Name' => 'A second name', 'Something' => 'Else'},
|
@@ -37,7 +37,7 @@ describe Executrix::Helper do
|
|
37
37
|
expect(described_class.records_to_csv(input)).to eq(expected_csv)
|
38
38
|
end
|
39
39
|
|
40
|
-
it '
|
40
|
+
it 'correctly converts Array to Multi-Picklist' do
|
41
41
|
input = [
|
42
42
|
{'Title' => 'Awesome Title', 'Picklist' => ['Several', 'Values']},
|
43
43
|
{'Title' => 'A second Title', 'Picklist' => ['SingleValue']},
|
@@ -49,7 +49,7 @@ describe Executrix::Helper do
|
|
49
49
|
expect(described_class.records_to_csv(input)).to eq(expected_csv)
|
50
50
|
end
|
51
51
|
|
52
|
-
it '
|
52
|
+
it 'returns valid csv when order of keys varies' do
|
53
53
|
input = [
|
54
54
|
{'Title' => 'Awesome Title', 'Name' => 'A name'},
|
55
55
|
{'Name' => 'A second name', 'Title' => 'A second Title'},
|
@@ -75,19 +75,146 @@ describe Executrix::Helper do
|
|
75
75
|
'https://supercustomname.my.salesforce.com/services/Soap/u/28.0/00EH0000001jNQu'
|
76
76
|
}
|
77
77
|
|
78
|
-
it '
|
78
|
+
it 'returns correct instance for regular salesforce server url' do
|
79
79
|
expect(described_class.fetch_instance_from_server_url(basic_server_url))
|
80
80
|
.to eq('eu1')
|
81
81
|
end
|
82
82
|
|
83
|
-
it '
|
83
|
+
it 'returns correct instance for api salesforce server url' do
|
84
84
|
expect(described_class.fetch_instance_from_server_url(basic_api_server_url))
|
85
85
|
.to eq('cs7')
|
86
86
|
end
|
87
87
|
|
88
|
-
it '
|
88
|
+
it 'returns correct instance for named salesforce server url' do
|
89
89
|
expect(described_class.fetch_instance_from_server_url(named_server_url))
|
90
90
|
.to eq('supercustomname.my')
|
91
91
|
end
|
92
92
|
end
|
93
|
+
|
94
|
+
describe '.attachment_keys' do
|
95
|
+
let(:records_with_attachment) do
|
96
|
+
prefix = Dir.tmpdir
|
97
|
+
FileUtils.touch("#{prefix}/attachment.pdf")
|
98
|
+
[
|
99
|
+
{
|
100
|
+
'normal_key' => 'normal_value1',
|
101
|
+
'attachment_key' => nil,
|
102
|
+
},
|
103
|
+
{
|
104
|
+
'normal_key' => 'normal_value2',
|
105
|
+
'attachment_key' => File.new("#{prefix}/attachment.pdf"),
|
106
|
+
}
|
107
|
+
]
|
108
|
+
end
|
109
|
+
|
110
|
+
let(:records_with_multiple_attachment) do
|
111
|
+
prefix = Dir.tmpdir
|
112
|
+
FileUtils.touch("#{prefix}/attachment1.pdf")
|
113
|
+
FileUtils.touch("#{prefix}/attachment2.pdf")
|
114
|
+
FileUtils.touch("#{prefix}/attachment3.pdf")
|
115
|
+
[
|
116
|
+
{
|
117
|
+
'normal_key' => 'normal_value1',
|
118
|
+
'attachment_key' => File.new("#{prefix}/attachment1.pdf"),
|
119
|
+
'another_attachment_key' => File.new("#{prefix}/attachment2.pdf"),
|
120
|
+
},
|
121
|
+
{
|
122
|
+
'normal_key' => 'normal_value2',
|
123
|
+
'attachment_key' => File.new("#{prefix}/attachment3.pdf"),
|
124
|
+
}
|
125
|
+
]
|
126
|
+
end
|
127
|
+
|
128
|
+
let(:records_without_attachment) do
|
129
|
+
[
|
130
|
+
{
|
131
|
+
'normal_key' => 'normal_value1',
|
132
|
+
'another_normal_key' => 'another_normal_value1',
|
133
|
+
},
|
134
|
+
{
|
135
|
+
'normal_key' => 'normal_value2',
|
136
|
+
'another_normal_key' => 'another_normal_value2',
|
137
|
+
}
|
138
|
+
]
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'returns correct keys for single attachment key' do
|
142
|
+
expect(described_class.attachment_keys(records_with_attachment))
|
143
|
+
.to eq(['attachment_key'])
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'returns correct keys for multiple attachment keys' do
|
147
|
+
expect(described_class.attachment_keys(records_with_multiple_attachment))
|
148
|
+
.to eq(['attachment_key', 'another_attachment_key'])
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'returns false for no attachment' do
|
152
|
+
expect(described_class.attachment_keys(records_without_attachment))
|
153
|
+
.to eq([])
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe '.transform_values!' do
|
158
|
+
let(:records_with_attachment) do
|
159
|
+
prefix = Dir.tmpdir
|
160
|
+
FileUtils.touch("#{prefix}/attachment.pdf")
|
161
|
+
[
|
162
|
+
{
|
163
|
+
'normal_key' => 'normal_value1',
|
164
|
+
'attachment_key' => nil,
|
165
|
+
},
|
166
|
+
{
|
167
|
+
'normal_key' => 'normal_value2',
|
168
|
+
'attachment_key' => File.new("#{prefix}/attachment.pdf"),
|
169
|
+
}
|
170
|
+
]
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'transforms values correctly' do
|
174
|
+
expect(File).to receive(:absolute_path).and_return('/an/absolute/path')
|
175
|
+
expected_output = [
|
176
|
+
{
|
177
|
+
'normal_key' => 'normal_value1',
|
178
|
+
'attachment_key' => nil,
|
179
|
+
},
|
180
|
+
{
|
181
|
+
'normal_key' => 'normal_value2',
|
182
|
+
'attachment_key' => '#an/absolute/path',
|
183
|
+
}
|
184
|
+
]
|
185
|
+
|
186
|
+
input = records_with_attachment
|
187
|
+
described_class.transform_values!(input,['attachment_key'])
|
188
|
+
expect(input).to eq(expected_output)
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'yields absolute path' do
|
192
|
+
expect(File).to receive(:absolute_path).and_return('/an/absolute/path')
|
193
|
+
input = records_with_attachment
|
194
|
+
expect do |blk|
|
195
|
+
described_class.transform_values!(input,['attachment_key'], &blk)
|
196
|
+
end.to yield_with_args('/an/absolute/path')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe '.absolute_to_relative_path' do
|
201
|
+
let(:unix_path) { '/a/unix/path' }
|
202
|
+
let(:windows_path_backslash) { 'C:\a\backslash\path' }
|
203
|
+
let(:windows_path_forwardslash) { 'C:/a/forwardslash/path' }
|
204
|
+
|
205
|
+
it 'strips unix path correctly' do
|
206
|
+
expect(described_class.absolute_to_relative_path(unix_path,'')).
|
207
|
+
to eq('a/unix/path')
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'strips windows path with backslash correctly' do
|
211
|
+
expect(described_class.absolute_to_relative_path(windows_path_backslash,'')).
|
212
|
+
to eq('a\backslash\path')
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'strips windows path with forwardslash correctly' do
|
216
|
+
expect(described_class.absolute_to_relative_path(windows_path_forwardslash,'')).
|
217
|
+
to eq('a/forwardslash/path')
|
218
|
+
end
|
219
|
+
end
|
93
220
|
end
|
@@ -16,7 +16,7 @@ describe Executrix::Http do
|
|
16
16
|
Executrix::Http::Request.new(:get, 'test.host', '/', '', [])
|
17
17
|
end
|
18
18
|
|
19
|
-
it '
|
19
|
+
it 'returns a response object' do
|
20
20
|
expected_body = 'correct result'
|
21
21
|
stub_request(:post, 'https://test.host')
|
22
22
|
.with(
|
@@ -140,16 +140,16 @@ describe Executrix::Http do
|
|
140
140
|
</soapenv:Envelope>}
|
141
141
|
end
|
142
142
|
|
143
|
-
it '
|
144
|
-
Executrix::Http.
|
143
|
+
it 'raises an error for faulty login' do
|
144
|
+
expect(Executrix::Http).to receive(:process_http_request)
|
145
145
|
.and_return(login_error)
|
146
146
|
expect{ Executrix::Http.login('a','b','c', 'd') }
|
147
147
|
.to raise_error(RuntimeError, login_error_message)
|
148
148
|
end
|
149
149
|
|
150
|
-
it '
|
150
|
+
it 'returns hash for correct login' do
|
151
151
|
[login_success, login_success_new].each do |login_response|
|
152
|
-
Executrix::Http.
|
152
|
+
expect(Executrix::Http).to receive(:process_http_request)
|
153
153
|
.and_return(login_response)
|
154
154
|
result = Executrix::Http.login('a','b','c', 'd')
|
155
155
|
expect(result).to be_a(Hash)
|
@@ -189,10 +189,10 @@ describe Executrix::Http do
|
|
189
189
|
</jobInfo>}
|
190
190
|
end
|
191
191
|
|
192
|
-
it '
|
193
|
-
Executrix::Http.
|
192
|
+
it 'returns hash for creating job' do
|
193
|
+
expect(Executrix::Http).to receive(:process_http_request)
|
194
194
|
.and_return(create_job_success)
|
195
|
-
result = Executrix::Http.create_job('a','b','c','d', 'e')
|
195
|
+
result = Executrix::Http.create_job('a','b','c','d', 'e', 'f')
|
196
196
|
expect(result).to be_a(Hash)
|
197
197
|
expect(result).to have_key(:id)
|
198
198
|
expect(result).to have_key(:operation)
|
@@ -218,8 +218,8 @@ describe Executrix::Http do
|
|
218
218
|
}
|
219
219
|
end
|
220
220
|
|
221
|
-
it '
|
222
|
-
Executrix::Http.
|
221
|
+
it 'returns hash for adding batch' do
|
222
|
+
expect(Executrix::Http).to receive(:process_http_request)
|
223
223
|
.and_return(add_batch_success)
|
224
224
|
result = Executrix::Http.add_batch(:post,'a','b','c','d')
|
225
225
|
expect(result).to be_a(Hash)
|
@@ -258,8 +258,8 @@ describe Executrix::Http do
|
|
258
258
|
</jobInfo>}
|
259
259
|
end
|
260
260
|
|
261
|
-
it '
|
262
|
-
Executrix::Http.
|
261
|
+
it 'returns hash for closing job' do
|
262
|
+
expect(Executrix::Http).to receive(:process_http_request)
|
263
263
|
.and_return(close_job_success)
|
264
264
|
result = Executrix::Http.close_job('a','b','c','d')
|
265
265
|
expect(result).to be_a(Hash)
|
@@ -280,8 +280,8 @@ describe Executrix::Http do
|
|
280
280
|
</error>}
|
281
281
|
end
|
282
282
|
|
283
|
-
it '
|
284
|
-
Executrix::Http.
|
283
|
+
it 'raises an exception on faulty authorization' do
|
284
|
+
expect(Executrix::Http).to receive(:process_http_request)
|
285
285
|
.and_return(invalid_session_id)
|
286
286
|
expect{Executrix::Http.query_batch('a','b','c','d','e')}
|
287
287
|
.to raise_error(RuntimeError, 'InvalidSessionId: Invalid session id')
|
@@ -295,8 +295,8 @@ describe Executrix::Http do
|
|
295
295
|
</result-list>}
|
296
296
|
end
|
297
297
|
|
298
|
-
it '
|
299
|
-
Executrix::Http.
|
298
|
+
it 'returns hash including the result id' do
|
299
|
+
expect(Executrix::Http).to receive(:process_http_request)
|
300
300
|
.and_return(batch_result_success)
|
301
301
|
result = Executrix::Http.query_batch_result_id('a','b','c','d','e')
|
302
302
|
expect(result).to be_a(Hash)
|
@@ -317,8 +317,8 @@ describe Executrix::Http do
|
|
317
317
|
"003K001200KO82cIAD","King of the Hill"}
|
318
318
|
end
|
319
319
|
|
320
|
-
it '
|
321
|
-
Executrix::Http.
|
320
|
+
it 'returns array of arrays for data' do
|
321
|
+
expect(Executrix::Http).to receive(:process_http_request)
|
322
322
|
.and_return(batch_result_data_success)
|
323
323
|
result = Executrix::Http.query_batch_result_data('a','b','c','d','e','f')
|
324
324
|
expect(result).to eq([
|
@@ -326,8 +326,8 @@ describe Executrix::Http do
|
|
326
326
|
{'Id' => '003M001200KO82cIAD', 'my_external_id__c' => 'K-015699'}])
|
327
327
|
end
|
328
328
|
|
329
|
-
it '
|
330
|
-
Executrix::Http.
|
329
|
+
it 'returns correct array with spaces' do
|
330
|
+
expect(Executrix::Http).to receive(:process_http_request)
|
331
331
|
.and_return(batch_result_data_with_spaces_success)
|
332
332
|
result = Executrix::Http.query_batch_result_data('a','b','c','d','e','f')
|
333
333
|
expect(result).to eq([
|
data/spec/lib/executrix_spec.rb
CHANGED
@@ -11,53 +11,96 @@ describe Executrix::Api do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
{
|
14
|
-
upsert:
|
15
|
-
update:
|
16
|
-
insert:
|
17
|
-
delete:
|
18
|
-
}.each do |method_name,
|
14
|
+
upsert: [nil,[{'no' => 'value'}], 'upsert_id'],
|
15
|
+
update: [nil,[{'no' => 'value'}]],
|
16
|
+
insert: [nil,[{'no' => 'value'}]],
|
17
|
+
delete: [nil,[{'no' => 'value'}]],
|
18
|
+
}.each do |method_name, values|
|
19
19
|
describe "##{method_name}" do
|
20
|
-
it '
|
21
|
-
Executrix::Connection
|
22
|
-
.
|
20
|
+
it 'delegates to #start_job' do
|
21
|
+
expect(Executrix::Connection)
|
22
|
+
.to receive(:connect)
|
23
23
|
.and_return(empty_connection)
|
24
24
|
s = described_class.new(nil, nil)
|
25
|
-
s.
|
26
|
-
.with(method_name.to_s, *
|
27
|
-
s.send(method_name, *
|
25
|
+
expect(s).to receive(:start_job)
|
26
|
+
.with(method_name.to_s, *values)
|
27
|
+
s.send(method_name, *values)
|
28
28
|
end
|
29
29
|
|
30
|
-
it '
|
31
|
-
Executrix::Connection
|
32
|
-
.
|
30
|
+
it 'triggers correct workflow' do
|
31
|
+
expect(Executrix::Connection)
|
32
|
+
.to receive(:connect)
|
33
33
|
.and_return(empty_connection)
|
34
34
|
s = described_class.new(nil, nil)
|
35
|
-
empty_connection.
|
36
|
-
empty_connection.
|
37
|
-
empty_connection.
|
38
|
-
res = s.send(method_name, *
|
35
|
+
expect(empty_connection).to receive(:create_job).ordered
|
36
|
+
expect(empty_connection).to receive(:add_batch).ordered
|
37
|
+
expect(empty_connection).to receive(:close_job).ordered
|
38
|
+
res = s.send(method_name, *values)
|
39
39
|
expect(res).to be_a(Executrix::Batch)
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
describe '#query' do
|
45
|
-
it '
|
46
|
-
|
47
|
-
.
|
45
|
+
it 'triggers correct workflow' do
|
46
|
+
expect(Executrix::Connection)
|
47
|
+
.to receive(:connect)
|
48
48
|
.and_return(empty_connection)
|
49
|
-
|
50
|
-
|
49
|
+
expect(Executrix::Batch)
|
50
|
+
.to receive(:new)
|
51
51
|
.and_return(empty_batch)
|
52
52
|
|
53
53
|
s = described_class.new(nil, nil)
|
54
54
|
sobject_input = 'sobject_stub'
|
55
55
|
query_input = 'query_stub'
|
56
|
-
empty_connection.
|
57
|
-
empty_connection.
|
58
|
-
empty_connection.
|
59
|
-
empty_batch.
|
56
|
+
expect(empty_connection).to receive(:create_job).ordered
|
57
|
+
expect(empty_connection).to receive(:add_query).ordered
|
58
|
+
expect(empty_connection).to receive(:close_job).ordered
|
59
|
+
expect(empty_batch).to receive(:final_status).ordered
|
60
60
|
s.query(sobject_input, query_input)
|
61
61
|
end
|
62
62
|
end
|
63
|
+
|
64
|
+
context 'file upload' do
|
65
|
+
describe '#insert' do
|
66
|
+
prefix = Dir.tmpdir
|
67
|
+
FileUtils.touch("#{prefix}/attachment.pdf")
|
68
|
+
attachment_data = {
|
69
|
+
'ParentId' => '00Kk0001908kqkDEAQ',
|
70
|
+
'Name' => 'attachment.pdf',
|
71
|
+
'Body' => File.new("#{prefix}/attachment.pdf")
|
72
|
+
}
|
73
|
+
|
74
|
+
{
|
75
|
+
upsert: [nil,[attachment_data.dup], 'upsert_id'],
|
76
|
+
update: [nil,[attachment_data.dup]],
|
77
|
+
insert: [nil,[attachment_data.dup]],
|
78
|
+
delete: [nil,[attachment_data.dup]],
|
79
|
+
}.each do |method_name, values|
|
80
|
+
describe "##{method_name}" do
|
81
|
+
it 'delegates to #start_job' do
|
82
|
+
expect(Executrix::Connection)
|
83
|
+
.to receive(:connect)
|
84
|
+
.and_return(empty_connection)
|
85
|
+
s = described_class.new(nil, nil)
|
86
|
+
expect(s).to receive(:start_job)
|
87
|
+
.with(method_name.to_s, *values)
|
88
|
+
s.send(method_name, *values)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'triggers correct workflow' do
|
92
|
+
expect(Executrix::Connection)
|
93
|
+
.to receive(:connect)
|
94
|
+
.and_return(empty_connection)
|
95
|
+
s = described_class.new(nil, nil)
|
96
|
+
expect(empty_connection).to receive(:create_job).ordered
|
97
|
+
expect(empty_connection).to receive(:add_file_upload_batch).ordered
|
98
|
+
expect(empty_connection).to receive(:close_job).ordered
|
99
|
+
res = s.send(method_name, *values)
|
100
|
+
expect(res).to be_a(Executrix::Batch)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
63
106
|
end
|
metadata
CHANGED
@@ -1,99 +1,114 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: executrix
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.2.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jorge Valdivia
|
9
9
|
- Leif Gensert
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-08-30 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake
|
17
|
-
|
18
|
-
none: false
|
17
|
+
version_requirements: !ruby/object:Gem::Requirement
|
19
18
|
requirements:
|
20
|
-
- -
|
19
|
+
- - '>='
|
21
20
|
- !ruby/object:Gem::Version
|
22
21
|
version: '0'
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
22
|
none: false
|
23
|
+
requirement: !ruby/object:Gem::Requirement
|
27
24
|
requirements:
|
28
|
-
- -
|
25
|
+
- - '>='
|
29
26
|
- !ruby/object:Gem::Version
|
30
27
|
version: '0'
|
28
|
+
none: false
|
29
|
+
prerelease: false
|
30
|
+
type: :runtime
|
31
31
|
- !ruby/object:Gem::Dependency
|
32
32
|
name: nori
|
33
|
-
|
34
|
-
none: false
|
33
|
+
version_requirements: !ruby/object:Gem::Requirement
|
35
34
|
requirements:
|
36
35
|
- - <
|
37
36
|
- !ruby/object:Gem::Version
|
38
|
-
version: '2.
|
39
|
-
type: :runtime
|
40
|
-
prerelease: false
|
41
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
version: '2.4'
|
42
38
|
none: false
|
39
|
+
requirement: !ruby/object:Gem::Requirement
|
43
40
|
requirements:
|
44
41
|
- - <
|
45
42
|
- !ruby/object:Gem::Version
|
46
|
-
version: '2.
|
43
|
+
version: '2.4'
|
44
|
+
none: false
|
45
|
+
prerelease: false
|
46
|
+
type: :runtime
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: nokogiri
|
49
|
-
|
50
|
-
none: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
50
|
requirements:
|
52
51
|
- - <
|
53
52
|
- !ruby/object:Gem::Version
|
54
53
|
version: '1.7'
|
55
|
-
type: :runtime
|
56
|
-
prerelease: false
|
57
|
-
version_requirements: !ruby/object:Gem::Requirement
|
58
54
|
none: false
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
59
56
|
requirements:
|
60
57
|
- - <
|
61
58
|
- !ruby/object:Gem::Version
|
62
59
|
version: '1.7'
|
60
|
+
none: false
|
61
|
+
prerelease: false
|
62
|
+
type: :runtime
|
63
63
|
- !ruby/object:Gem::Dependency
|
64
|
-
name:
|
65
|
-
|
64
|
+
name: rubyzip
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - <
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.1'
|
66
70
|
none: false
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
67
72
|
requirements:
|
68
73
|
- - <
|
69
74
|
- !ruby/object:Gem::Version
|
70
|
-
version: '
|
71
|
-
|
75
|
+
version: '1.1'
|
76
|
+
none: false
|
72
77
|
prerelease: false
|
78
|
+
type: :runtime
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: rspec
|
73
81
|
version_requirements: !ruby/object:Gem::Requirement
|
74
|
-
none: false
|
75
82
|
requirements:
|
76
83
|
- - <
|
77
84
|
- !ruby/object:Gem::Version
|
78
|
-
version: '2.
|
79
|
-
- !ruby/object:Gem::Dependency
|
80
|
-
name: webmock
|
81
|
-
requirement: !ruby/object:Gem::Requirement
|
85
|
+
version: '2.15'
|
82
86
|
none: false
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
83
88
|
requirements:
|
84
89
|
- - <
|
85
90
|
- !ruby/object:Gem::Version
|
86
|
-
version: '
|
87
|
-
|
91
|
+
version: '2.15'
|
92
|
+
none: false
|
88
93
|
prerelease: false
|
94
|
+
type: :development
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: webmock
|
89
97
|
version_requirements: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - <
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.14'
|
90
102
|
none: false
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
91
104
|
requirements:
|
92
105
|
- - <
|
93
106
|
- !ruby/object:Gem::Version
|
94
|
-
version: '1.
|
95
|
-
|
96
|
-
|
107
|
+
version: '1.14'
|
108
|
+
none: false
|
109
|
+
prerelease: false
|
110
|
+
type: :development
|
111
|
+
description: This gem provides a super simple interface for the Salesforce Bulk API. It provides support for insert, update, upsert, delete, and query.
|
97
112
|
email:
|
98
113
|
- jorge@valdivia.me
|
99
114
|
- leif@propertybase.com
|
@@ -125,32 +140,32 @@ files:
|
|
125
140
|
- spec/spec_helper.rb
|
126
141
|
homepage: https://github.com/propertybase/executrix
|
127
142
|
licenses: []
|
128
|
-
post_install_message:
|
143
|
+
post_install_message:
|
129
144
|
rdoc_options: []
|
130
145
|
require_paths:
|
131
146
|
- lib
|
132
147
|
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
-
none: false
|
134
148
|
requirements:
|
135
|
-
- -
|
149
|
+
- - '>='
|
136
150
|
- !ruby/object:Gem::Version
|
137
|
-
version: '0'
|
138
151
|
segments:
|
139
152
|
- 0
|
140
|
-
|
141
|
-
|
153
|
+
version: '0'
|
154
|
+
hash: 2
|
142
155
|
none: false
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
143
157
|
requirements:
|
144
|
-
- -
|
158
|
+
- - '>='
|
145
159
|
- !ruby/object:Gem::Version
|
146
|
-
version: '0'
|
147
160
|
segments:
|
148
161
|
- 0
|
149
|
-
|
162
|
+
version: '0'
|
163
|
+
hash: 2
|
164
|
+
none: false
|
150
165
|
requirements: []
|
151
166
|
rubyforge_project: executrix
|
152
|
-
rubygems_version: 1.8.
|
153
|
-
signing_key:
|
167
|
+
rubygems_version: 1.8.24
|
168
|
+
signing_key:
|
154
169
|
specification_version: 3
|
155
170
|
summary: Ruby support for the Salesforce Bulk API
|
156
171
|
test_files:
|