4me-sdk 1.1.2 → 1.1.3
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 +4 -4
- data/4me-sdk.gemspec +1 -1
- data/README.md +3 -3
- data/lib/sdk4me/client/version.rb +1 -1
- data/spec/lib/sdk4me/attachments_spec.rb +178 -0
- data/spec/lib/sdk4me/certificate_spec.rb +28 -0
- data/spec/lib/sdk4me/client_spec.rb +543 -0
- data/spec/lib/sdk4me/response_spec.rb +271 -0
- data/spec/lib/sdk4me_spec.rb +33 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/support/fixtures/people.csv +3 -0
- data/spec/support/fixtures/upload.txt +1 -0
- data/spec/support/matchers/never_raise.rb +46 -0
- data/spec/support/util.rb +3 -0
- metadata +23 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b97eb59b35a6a8b7551637c38240a5bf2c76b8e
|
|
4
|
+
data.tar.gz: 625d0ffaf906d4ebcab85012bc4a7627ed17b6c3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6803ef3bebc9ca50c967a0e975f5062ed233b8f435dc32e985e33b5b7099c24b7d06895304fb5103b5831a2f55d37c523bf2e39f766410de69f1d3e6bb8d95be
|
|
7
|
+
data.tar.gz: 2933d2f59bde6498efadeb177988eea3c99e6a7ec35fe59db175f7c11731064dd7ce0d3aafd982cdf05e8b66afac54c986cd476e821746bbec632dc96a555cf1
|
data/4me-sdk.gemspec
CHANGED
|
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
|
10
10
|
spec.required_ruby_version = '>= 2.0.0'
|
|
11
11
|
spec.authors = ['4me']
|
|
12
12
|
spec.email = %q{developers@4me.com}
|
|
13
|
-
spec.description = %q{SDK for accessing the 4me}
|
|
13
|
+
spec.description = %q{SDK for accessing the 4me API}
|
|
14
14
|
spec.summary = %q{The official 4me SDK for Ruby. Provides easy access to the APIs found at https://developer.4me.com}
|
|
15
15
|
spec.homepage = %q{https://github.com/code4me/4me-sdk-ruby}
|
|
16
16
|
spec.license = 'MIT'
|
data/README.md
CHANGED
|
@@ -51,7 +51,7 @@ All options available:
|
|
|
51
51
|
|
|
52
52
|
### Override
|
|
53
53
|
|
|
54
|
-
Each time an 4me SDK Client is instantiated it is possible to override the [global configuration](#global
|
|
54
|
+
Each time an 4me SDK Client is instantiated it is possible to override the [global configuration](#global) like so:
|
|
55
55
|
|
|
56
56
|
```
|
|
57
57
|
client = Sdk4me::Client.new(account: 'trusted-sandbox', source: 'my special integration')
|
|
@@ -304,9 +304,9 @@ Note that blocking for the export to finish is recommended as you will get direc
|
|
|
304
304
|
|
|
305
305
|
### Blocking
|
|
306
306
|
|
|
307
|
-
By default all actions on the 4me SDK Client will block until the 4me API is accessible, see the _max_retry_time_ option in the [configuration](#
|
|
307
|
+
By default all actions on the 4me SDK Client will block until the 4me API is accessible, see the _max_retry_time_ option in the [configuration](#configuration). This is especially helpfull for flaky internet connections.
|
|
308
308
|
|
|
309
|
-
By setting the _block_at_rate_limit_ to `true` in the [configuration](#
|
|
309
|
+
By setting the _block_at_rate_limit_ to `true` in the [configuration](#configuration) all actions will also block in case the [rate limit](http://developer.4me.com/v1/#rate-limiting) is reached. The action is retried every 5 minutes until the [rate limit](http://developer.4me.com/v1/#rate-limiting) is lifted again, which might take up to 1 hour.
|
|
310
310
|
|
|
311
311
|
|
|
312
312
|
### Exception handling
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Sdk4me::Attachments do
|
|
4
|
+
|
|
5
|
+
before(:each) do
|
|
6
|
+
@client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
7
|
+
@attachments = Sdk4me::Attachments.new(@client)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
context 'upload_attachments!' do
|
|
11
|
+
it 'should not do anything when no :attachments are present' do
|
|
12
|
+
expect(@attachments.upload_attachments!('/requests', {status: :in_progress})).to be_nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'should not do anything when :attachments is nil' do
|
|
16
|
+
expect(@attachments.upload_attachments!('/requests', {attachments: nil})).to be_nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'should not do anything when :attachments is empty' do
|
|
20
|
+
expect(@attachments.upload_attachments!('/requests', {attachments: []})).to be_nil
|
|
21
|
+
expect(@attachments.upload_attachments!('/requests', {attachments: [nil]})).to be_nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'should show a error if no attachment may be uploaded' do
|
|
25
|
+
stub_request(:get, 'https://api.4me.com/v1/sites/1?attachment_upload_token=true').with(basic_auth: ['secret', 'x']).to_return(body: {name: 'site 1'}.to_json)
|
|
26
|
+
expect_log('Attachments not allowed for /sites/1', :error)
|
|
27
|
+
expect(@attachments.upload_attachments!('/sites/1', {attachments: ['file1.png']})).to be_nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'should raise an exception if no attachment may be uploaded' do
|
|
31
|
+
stub_request(:get, 'https://api.4me.com/v1/sites/1?attachment_upload_token=true').with(basic_auth: ['secret', 'x']).to_return(body: {name: 'site 1'}.to_json)
|
|
32
|
+
message = 'Attachments not allowed for /sites/1'
|
|
33
|
+
expect{ @attachments.upload_attachments!('/sites/1', {attachments: ['file1.png'], attachments_exception: true}) }.to raise_error(::Sdk4me::UploadFailed, message)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'should add /new to the path for new records' do
|
|
37
|
+
stub_request(:get, 'https://api.4me.com/v1/sites/new?attachment_upload_token=true').with(basic_auth: ['secret', 'x']).to_return(body: {missing: 'storage'}.to_json)
|
|
38
|
+
expect_log('Attachments not allowed for /sites', :error)
|
|
39
|
+
expect(@attachments.upload_attachments!('/sites', {attachments: ['file1.png']})).to be_nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
[ [:requests, :note],
|
|
43
|
+
[:problems, :note],
|
|
44
|
+
[:contracts, :remarks],
|
|
45
|
+
[:cis, :remarks],
|
|
46
|
+
[:flsas, :remarks],
|
|
47
|
+
[:slas, :remarks],
|
|
48
|
+
[:service_instances, :remarks],
|
|
49
|
+
[:service_offerings, :summary],
|
|
50
|
+
[:any_other_model, :note]].each do |model, attribute|
|
|
51
|
+
|
|
52
|
+
it "should replace :attachments with :#{attribute}_attachments after upload at /#{model}" do
|
|
53
|
+
stub_request(:get, "https://api.4me.com/v1/#{model}/new?attachment_upload_token=true").with(basic_auth: ['secret', 'x']).to_return(body: {storage_upload: 'conf'}.to_json)
|
|
54
|
+
expect(@attachments).to receive(:upload_attachment).with('conf', 'file1.png', false).ordered{ 'uploaded file1.png' }
|
|
55
|
+
expect(@attachments).to receive(:upload_attachment).with('conf', 'file2.zip', false).ordered{ 'uploaded file2.zip' }
|
|
56
|
+
data = {leave: 'me alone', attachments: %w(file1.png file2.zip)}
|
|
57
|
+
@attachments.upload_attachments!("/#{model}", data)
|
|
58
|
+
expect(data[:attachments]).to be_nil
|
|
59
|
+
expect(data[:leave]).to eq('me alone')
|
|
60
|
+
expect(data[:"#{attribute}_attachments"]).to eq(['uploaded file1.png', 'uploaded file2.zip'].to_json)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'should set raise_exception flag to true when :attachments_exception is set' do
|
|
65
|
+
stub_request(:get, 'https://api.4me.com/v1/requests/new?attachment_upload_token=true').with(basic_auth: ['secret', 'x']).to_return(body: {storage_upload: 'conf'}.to_json)
|
|
66
|
+
expect(@attachments).to receive(:upload_attachment).with('conf', 'file1.png', true).ordered{ 'uploaded file1.png' }
|
|
67
|
+
data = {leave: 'me alone', attachments: 'file1.png', attachments_exception: true}
|
|
68
|
+
@attachments.upload_attachments!('/requests', data)
|
|
69
|
+
expect(data[:attachments]).to be_nil
|
|
70
|
+
expect(data[:attachments_exception]).to be_nil
|
|
71
|
+
expect(data[:leave]).to eq('me alone')
|
|
72
|
+
expect(data[:note_attachments]).to eq(['uploaded file1.png'].to_json)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
context 'upload_attachment' do
|
|
77
|
+
|
|
78
|
+
it 'should log an exception when the file could not be found' do
|
|
79
|
+
expect_log('Attachment upload failed: file does not exist: unknown_file', :error)
|
|
80
|
+
expect(@attachments.send(:upload_attachment, nil, 'unknown_file', false)).to be_nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'should raise an exception when the file could not be found' do
|
|
84
|
+
message = 'Attachment upload failed: file does not exist: unknown_file'
|
|
85
|
+
expect{ @attachments.send(:upload_attachment, nil, 'unknown_file', true) }.to raise_error(::Sdk4me::UploadFailed, message)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context 'aws' do
|
|
89
|
+
before(:each) do
|
|
90
|
+
@aws_conf = {
|
|
91
|
+
provider: 'aws',
|
|
92
|
+
upload_uri: 'https://itrp.s3.amazonaws.com/',
|
|
93
|
+
access_key: 'AKIA6RYQ',
|
|
94
|
+
success_url: 'https://mycompany.4me.com/s3_success?sig=99e82e8a046',
|
|
95
|
+
policy: 'eydlgIH0=',
|
|
96
|
+
signature: 'nbhdec4k=',
|
|
97
|
+
upload_path: 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/'
|
|
98
|
+
}
|
|
99
|
+
@key_template = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}'
|
|
100
|
+
@key = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/upload.txt'
|
|
101
|
+
|
|
102
|
+
@multi_part_body = "--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"Content-Type\"\r\n\r\napplication/octet-stream\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"x-amz-server-side-encryption\"\r\n\r\nAES256\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\nattachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"AWSAccessKeyId\"\r\n\r\nAKIA6RYQ\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"acl\"\r\n\r\nprivate\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"signature\"\r\n\r\nnbhdec4k=\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"policy\"\r\n\r\neydlgIH0=\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{@fixture_dir}/upload.txt\"\r\nContent-Type: text/plain\r\n\r\ncontent\r\n--0123456789ABLEWASIEREISAWELBA9876543210--"
|
|
103
|
+
@multi_part_headers = {'Accept'=>'*/*', 'Content-Type'=>'multipart/form-data; boundary=0123456789ABLEWASIEREISAWELBA9876543210', 'User-Agent'=>'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6'}
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'should open a file from disk' do
|
|
107
|
+
expect(@attachments).to receive(:aws_upload).with(@aws_conf, @key_template, @key, kind_of(File))
|
|
108
|
+
expect(@attachments.send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'should sent the upload to AWS' do
|
|
112
|
+
stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: 'OK', status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
|
|
113
|
+
stub_request(:get, "https://api.4me.com/v1/s3_success?sig=99e82e8a046&key=#{@key}").with(basic_auth: ['secret', 'x']).to_return(body: {}.to_json)
|
|
114
|
+
expect(@attachments.send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'should report an error when AWS upload fails' do
|
|
118
|
+
stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: %(<?xml version="1.0" encoding="UTF-8"?>\n<Error><Code>AccessDenied</Code><Message>Invalid according to Policy</Message><RequestId>1FECC4B719E426B1</RequestId><HostId>15+14lXt+HlF</HostId></Error>), status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
|
|
119
|
+
expect_log("Attachment upload failed: AWS upload to https://itrp.s3.amazonaws.com/ for #{@key} failed: Invalid according to Policy", :error)
|
|
120
|
+
expect(@attachments.send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
it 'should report an error when 4me confirmation fails' do
|
|
124
|
+
stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: 'OK', status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
|
|
125
|
+
stub_request(:get, "https://api.4me.com/v1/s3_success?sig=99e82e8a046&key=#{@key}").with(basic_auth: ['secret', 'x']).to_return(body: {message: 'oops!'}.to_json)
|
|
126
|
+
expect_log('Request failed: oops!', :error)
|
|
127
|
+
expect_log("Attachment upload failed: 4me confirmation s3_success?sig=99e82e8a046 for #{@key} failed: oops!", :error)
|
|
128
|
+
expect(@attachments.send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'should raise an exception when AWS upload fails' do
|
|
132
|
+
stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: %(<?xml version="1.0" encoding="UTF-8"?>\n<Error><Code>AccessDenied</Code><Message>Invalid according to Policy</Message><RequestId>1FECC4B719E426B1</RequestId><HostId>15+14lXt+HlF</HostId></Error>), status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
|
|
133
|
+
message = "Attachment upload failed: AWS upload to https://itrp.s3.amazonaws.com/ for #{@key} failed: Invalid according to Policy"
|
|
134
|
+
expect{ @attachments.send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", true) }.to raise_error(::Sdk4me::UploadFailed, message)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context '4me' do
|
|
139
|
+
before(:each) do
|
|
140
|
+
@sdk4me_conf = {
|
|
141
|
+
provider: 'local',
|
|
142
|
+
upload_uri: 'https://api.4me.com/attachments',
|
|
143
|
+
upload_path: 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/'
|
|
144
|
+
}
|
|
145
|
+
@key_template = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}'
|
|
146
|
+
@key = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/upload.txt'
|
|
147
|
+
|
|
148
|
+
@multi_part_body = "--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"Content-Type\"\r\n\r\napplication/octet-stream\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{@spec_dir}/support/fixtures/upload.txt\"\r\nContent-Type: text/plain\r\n\r\ncontent\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\nattachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}\r\n--0123456789ABLEWASIEREISAWELBA9876543210--"
|
|
149
|
+
@multi_part_headers = {'Accept'=>'*/*', 'Content-Type'=>'multipart/form-data; boundary=0123456789ABLEWASIEREISAWELBA9876543210', 'User-Agent'=>'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6'}
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'should open a file from disk' do
|
|
153
|
+
expect(@attachments).to receive(:upload_to_4me).with(@sdk4me_conf, @key_template, @key, kind_of(File))
|
|
154
|
+
expect(@attachments.send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'should sent the upload to 4me' do
|
|
158
|
+
stub_request(:post, 'https://api.4me.com/v1/attachments').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {}.to_json)
|
|
159
|
+
expect(@attachments.send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'should report an error when 4me upload fails' do
|
|
163
|
+
stub_request(:post, 'https://api.4me.com/v1/attachments').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
|
|
164
|
+
expect_log('Request failed: oops!', :error)
|
|
165
|
+
expect_log("Attachment upload failed: 4me upload to https://api.4me.com/attachments for #{@key} failed: oops!", :error)
|
|
166
|
+
expect(@attachments.send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it 'should raise an exception when 4me upload fails' do
|
|
170
|
+
stub_request(:post, 'https://api.4me.com/v1/attachments').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
|
|
171
|
+
expect_log('Request failed: oops!', :error)
|
|
172
|
+
message = "Attachment upload failed: 4me upload to https://api.4me.com/attachments for #{@key} failed: oops!"
|
|
173
|
+
expect{ @attachments.send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", true) }.to raise_error(::Sdk4me::UploadFailed, message)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe 'ca-bundle.crt' do
|
|
4
|
+
|
|
5
|
+
it 'should be able to connect to the 4me API' do
|
|
6
|
+
WebMock.allow_net_connect!
|
|
7
|
+
client = Sdk4me::Client.new(api_token: 'invalid', max_retry_time: -1)
|
|
8
|
+
result = {}
|
|
9
|
+
|
|
10
|
+
# no exception concerning the certificate
|
|
11
|
+
expect { result[:response] = client.get('me') }.not_to raise_error
|
|
12
|
+
response = result[:response]
|
|
13
|
+
expect(response.valid?).to be_falsey
|
|
14
|
+
|
|
15
|
+
# expecting 401 error
|
|
16
|
+
expect(response.message).to eq('401: Access credentials required')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'should be able to connect to S3' do
|
|
20
|
+
WebMock.allow_net_connect!
|
|
21
|
+
http = Net::HTTP.new('sdk4me-eu.s3-eu-west-1.amazonaws.com', 443)
|
|
22
|
+
http.read_timeout = 1
|
|
23
|
+
http.use_ssl = true
|
|
24
|
+
|
|
25
|
+
# no SSL error please
|
|
26
|
+
expect{ http.start{ |_http| _http.request(Net::HTTP::Get.new('/exports/20141107/')) } }.to never_raise(OpenSSL::SSL::SSLError)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Sdk4me::Client do
|
|
4
|
+
|
|
5
|
+
context 'Sdk4me.config' do
|
|
6
|
+
before(:each) do
|
|
7
|
+
Sdk4me.configure do |config|
|
|
8
|
+
config.max_retry_time = 120 # override default value (5400)
|
|
9
|
+
config.api_token = 'secret' # set value
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'should define the MAX_PAGE_SIZE' do
|
|
14
|
+
expect(Sdk4me::Client::MAX_PAGE_SIZE).to eq(100)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'should use the Sdk4me configuration' do
|
|
18
|
+
client = Sdk4me::Client.new
|
|
19
|
+
expect(client.option(:host)).to eq('https://api.4me.com') # default value
|
|
20
|
+
expect(client.option(:api_token)).to eq('secret') # value set using Sdk4me.config
|
|
21
|
+
expect(client.option(:max_retry_time)).to eq(120) # value overridden in Sdk4me.config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'should override the Sdk4me configuration' do
|
|
25
|
+
client = Sdk4me::Client.new(host: 'https://demo.4me.com', api_token: 'unknown', block_at_rate_limit: true)
|
|
26
|
+
expect(client.option(:read_timeout)).to eq(25) # default value
|
|
27
|
+
expect(client.option(:host)).to eq('https://demo.4me.com') # default value overridden in Client.new
|
|
28
|
+
expect(client.option(:api_token)).to eq('unknown') # value set using Sdk4me.config and overridden in Client.new
|
|
29
|
+
expect(client.option(:max_retry_time)).to eq(120) # value overridden in Sdk4me.config
|
|
30
|
+
expect(client.option(:block_at_rate_limit)).to eq(true) # value overridden in Client.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
[:host, :api_version, :api_token].each do |required_option|
|
|
34
|
+
it "should require option #{required_option}" do
|
|
35
|
+
expect { Sdk4me::Client.new(required_option => '') }.to raise_error("Missing required configuration option #{required_option}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
[ ['https://api.4me.com', true, 'api.4me.com', 443],
|
|
40
|
+
['https://api.example.com:777', true, 'api.example.com', 777],
|
|
41
|
+
['http://sdk4me.example.com', false, 'sdk4me.example.com', 80],
|
|
42
|
+
['http://sdk4me.example.com:777', false, 'sdk4me.example.com', 777]
|
|
43
|
+
].each do |host, ssl, domain, port|
|
|
44
|
+
it 'should parse ssl, host and port' do
|
|
45
|
+
client = Sdk4me::Client.new(host: host)
|
|
46
|
+
expect(client.instance_variable_get(:@ssl)).to eq(ssl)
|
|
47
|
+
expect(client.instance_variable_get(:@domain)).to eq(domain)
|
|
48
|
+
expect(client.instance_variable_get(:@port)).to eq(port)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'should set the ca-bundle.crt file' do
|
|
54
|
+
http = Net::HTTP.new('https://api.4me.com')
|
|
55
|
+
http.use_ssl = true
|
|
56
|
+
|
|
57
|
+
on_disk = `ls #{http.ca_file}`
|
|
58
|
+
expect(on_disk).not_to match(/cannot access/)
|
|
59
|
+
expect(on_disk).to match(/\/ca-bundle.crt$/)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe 'headers' do
|
|
63
|
+
before(:each) do
|
|
64
|
+
@client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'should set the content type header' do
|
|
68
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).with(headers: {'Content-Type' => 'application/json'}).to_return(body: {name: 'my name'}.to_json)
|
|
69
|
+
@client.get('me')
|
|
70
|
+
expect(stub).to have_been_requested
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'should add the X-4me-Account header' do
|
|
74
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1, account: 'test')
|
|
75
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).with(headers: {'X-4me-Account' => 'test'}).to_return(body: {name: 'my name'}.to_json)
|
|
76
|
+
client.get('me')
|
|
77
|
+
expect(stub).to have_been_requested
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'should add the X-4me-Source header' do
|
|
81
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1, source: 'myapp')
|
|
82
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).with(headers: {'X-4me-Source' => 'myapp'}).to_return(body: {name: 'my name'}.to_json)
|
|
83
|
+
client.get('me')
|
|
84
|
+
expect(stub).to have_been_requested
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'should be able to override headers' do
|
|
88
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).with(headers: {'Content-Type' => 'application/x-www-form-urlencoded'}).to_return(body: {name: 'my name'}.to_json)
|
|
89
|
+
@client.get('me', {}, {'Content-Type' => 'application/x-www-form-urlencoded'})
|
|
90
|
+
expect(stub).to have_been_requested
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'should set the other headers' do
|
|
94
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).with(headers: {'X-4me-Other' => 'value'}).to_return(body: {name: 'my name'}.to_json)
|
|
95
|
+
@client.get('me', {}, {'X-4me-Other' => 'value'})
|
|
96
|
+
expect(stub).to have_been_requested
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'should accept headers in the each call' do
|
|
100
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/requests?fields=subject&page=1&per_page=100').with(basic_auth: ['secret', 'x']).with(headers: {'X-4me-Secret' => 'special'}).to_return(body: [{id: 1, subject: 'Subject 1'}, {id: 2, subject: 'Subject 2'}, {id: 3, subject: 'Subject 3'}].to_json)
|
|
101
|
+
@client.each('requests', {fields: 'subject'}, {'X-4me-Secret' => 'special'}) do |request|
|
|
102
|
+
expect(request[:subject]).to eq("Subject #{request[:id]}")
|
|
103
|
+
end
|
|
104
|
+
expect(stub).to have_been_requested
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context 'each' do
|
|
109
|
+
before(:each) do
|
|
110
|
+
@client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'should yield each result' do
|
|
114
|
+
stub_request(:get, 'https://api.4me.com/v1/requests?fields=subject&page=1&per_page=100').with(basic_auth: ['secret', 'x']).to_return(body: [{id: 1, subject: 'Subject 1'}, {id: 2, subject: 'Subject 2'}, {id: 3, subject: 'Subject 3'}].to_json)
|
|
115
|
+
nr_of_requests = @client.each('requests', {fields: 'subject'}) do |request|
|
|
116
|
+
expect(request[:subject]).to eq("Subject #{request[:id]}")
|
|
117
|
+
end
|
|
118
|
+
expect(nr_of_requests).to eq(3)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'should retrieve multiple pages' do
|
|
122
|
+
stub_page1 = stub_request(:get, 'https://api.4me.com/v1/requests?page=1&per_page=2').with(basic_auth: ['secret', 'x']).to_return(body: [{id: 1, subject: 'Subject 1'}, {id: 2, subject: 'Subject 2'}].to_json, headers: {'Link' => '<https://api.4me.com/v1/requests?page=1&per_page=2>; rel="first",<https://api.4me.com/v1/requests?page=2&per_page=2>; rel="next",<https://api.4me.com/v1/requests?page=2&per_page=2>; rel="last"'})
|
|
123
|
+
stub_page2 = stub_request(:get, 'https://api.4me.com/v1/requests?page=2&per_page=2').with(basic_auth: ['secret', 'x']).to_return(body: [{id: 3, subject: 'Subject 3'}].to_json, headers: {'Link' => '<https://api.4me.com/v1/requests?page=1&per_page=2>; rel="first",<https://api.4me.com/v1/requests?page=1&per_page=2>; rel="prev",<https://api.4me.com/v1/requests?page=2&per_page=2>; rel="last"'})
|
|
124
|
+
nr_of_requests = @client.each('requests', {per_page: 2}) do |request|
|
|
125
|
+
expect(request[:subject]).to eq("Subject #{request[:id]}")
|
|
126
|
+
end
|
|
127
|
+
expect(nr_of_requests).to eq(3)
|
|
128
|
+
expect(stub_page2).to have_been_requested
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
context 'get' do
|
|
133
|
+
before(:each) do
|
|
134
|
+
@client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'should return a response' do
|
|
138
|
+
stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(body: {name: 'my name'}.to_json)
|
|
139
|
+
response = @client.get('me')
|
|
140
|
+
expect(response[:name]).to eq('my name')
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
describe 'parameters' do
|
|
144
|
+
|
|
145
|
+
[[nil, ''],
|
|
146
|
+
[ 'normal', 'normal'],
|
|
147
|
+
[ 'hello;<', 'hello%3B%3C'],
|
|
148
|
+
[ true, 'true'],
|
|
149
|
+
[ false, 'false'],
|
|
150
|
+
[ DateTime.now, DateTime.now.new_offset(0).iso8601.gsub('+', '%2B')],
|
|
151
|
+
[ Date.new, Date.new.strftime('%Y-%m-%d')],
|
|
152
|
+
[ Time.now, Time.now.strftime('%H:%M')],
|
|
153
|
+
[ ['first', 'second;<', true], 'first,second%3B%3C,true']
|
|
154
|
+
].each do |param_value, url_value|
|
|
155
|
+
it "should cast #{param_value.class.name}: '#{param_value}' to '#{url_value}'" do
|
|
156
|
+
stub = stub_request(:get, "https://api.4me.com/v1/me?value=#{url_value}").with(basic_auth: ['secret', 'x']).to_return(body: {name: 'my name'}.to_json)
|
|
157
|
+
@client.get('me', {value: param_value})
|
|
158
|
+
expect(stub).to have_been_requested
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'should not cast arrays in post and put calls' do
|
|
163
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
164
|
+
stub = stub_request(:post, 'https://api.4me.com/v1/people').with(basic_auth: ['secret', 'x']).with(body: {user_ids: [1, 2, 3]}, headers: {'X-4me-Custom' => 'custom'}).to_return(body: {id: 101}.to_json)
|
|
165
|
+
client.post('people', {user_ids: [1, 2, 3]}, {'X-4me-Custom' => 'custom'})
|
|
166
|
+
expect(stub).to have_been_requested
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
it 'should not cast hashes in post and put calls' do
|
|
170
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
171
|
+
stub = stub_request(:patch, 'https://api.4me.com/v1/people/55').with(basic_auth: ['secret', 'x']).with(body: '{"contacts_attributes":{"0":{"protocol":"email","label":"work","uri":"work@example.com"}}}', headers: {'X-4me-Custom' => 'custom'}).to_return(body: {id: 101}.to_json)
|
|
172
|
+
client.put('people/55', {contacts_attributes: {0 => {protocol: :email, label: :work, uri: 'work@example.com'}}}, {'X-4me-Custom' => 'custom'})
|
|
173
|
+
expect(stub).to have_been_requested
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'should not double escape symbols' do
|
|
177
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
178
|
+
stub = stub_request(:patch, 'https://api.4me.com/v1/people/55').with(basic_auth: ['secret', 'x']).with(body: '{"status":"waiting_for"}').to_return(body: {id: 101}.to_json)
|
|
179
|
+
client.put('people/55', {status: :waiting_for})
|
|
180
|
+
expect(stub).to have_been_requested
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it 'should handle fancy filter operations' do
|
|
184
|
+
now = DateTime.now
|
|
185
|
+
stub = stub_request(:get, "https://api.4me.com/v1/people?created_at=>#{now.new_offset(0).iso8601.gsub('+', '%2B')}&id!=15").with(basic_auth: ['secret', 'x']).to_return(body: {name: 'my name'}.to_json)
|
|
186
|
+
@client.get('people', {'created_at=>' => now, 'id!=' => 15})
|
|
187
|
+
expect(stub).to have_been_requested
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'should append parameters' do
|
|
191
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/people?id!=15&primary_email=me@example.com').with(basic_auth: ['secret', 'x']).to_return(body: {name: 'my name'}.to_json)
|
|
192
|
+
@client.get('people?id!=15', {primary_email: 'me@example.com'})
|
|
193
|
+
expect(stub).to have_been_requested
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
context 'patch' do
|
|
199
|
+
[:put, :patch].each do |method|
|
|
200
|
+
it 'should send patch requests with parameters and headers for #{method} calls' do
|
|
201
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
202
|
+
stub = stub_request(:patch, 'https://api.4me.com/v1/people/1').with(basic_auth: ['secret', 'x']).with(body: {name: 'New Name'}, headers: {'X-4me-Custom' => 'custom'}).to_return(body: {id: 1}.to_json)
|
|
203
|
+
client.send(method, 'people/1', {name: 'New Name'}, {'X-4me-Custom' => 'custom'})
|
|
204
|
+
expect(stub).to have_been_requested
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context 'post' do
|
|
210
|
+
it 'should send post requests with parameters and headers' do
|
|
211
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
212
|
+
stub = stub_request(:post, 'https://api.4me.com/v1/people').with(basic_auth: ['secret', 'x']).with(body: {name: 'New Name'}, headers: {'X-4me-Custom' => 'custom'}).to_return(body: {id: 101}.to_json)
|
|
213
|
+
client.post('people', {name: 'New Name'}, {'X-4me-Custom' => 'custom'})
|
|
214
|
+
expect(stub).to have_been_requested
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
context 'delete' do
|
|
219
|
+
it 'should send delete requests with parameters and headers' do
|
|
220
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
221
|
+
stub = stub_request(:delete, 'https://api.4me.com/v1/people?id=value').with(basic_auth: ['secret', 'x']).with(headers: {'X-4me-Custom' => 'custom'}).to_return(body: '', status: 204)
|
|
222
|
+
response = client.delete('people', {id: 'value'}, {'X-4me-Custom' => 'custom'})
|
|
223
|
+
expect(stub).to have_been_requested
|
|
224
|
+
expect(response.valid?).to be_truthy
|
|
225
|
+
expect(response.json).to eq({})
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
context 'attachments' do
|
|
230
|
+
before(:each) do
|
|
231
|
+
@client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
it 'should not log an error for XML responses' do
|
|
235
|
+
xml = %(<?xml version="1.0" encoding="UTF-8"?>\n<details>some info</details>)
|
|
236
|
+
stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(body: xml)
|
|
237
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug)
|
|
238
|
+
expect_log("XML response:\n#{xml}", :debug)
|
|
239
|
+
response = @client.get('me')
|
|
240
|
+
expect(response.valid?).to be_falsey
|
|
241
|
+
expect(response.raw.body).to eq(xml)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'should not log an error for redirects' do
|
|
245
|
+
stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(body: '', status: 303, headers: {'Location' => 'http://redirect.example.com/to/here'})
|
|
246
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug)
|
|
247
|
+
expect_log('Redirect: http://redirect.example.com/to/here', :debug)
|
|
248
|
+
response = @client.get('me')
|
|
249
|
+
expect(response.valid?).to be_falsey
|
|
250
|
+
expect(response.raw.body).to be_nil
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
it "should not parse attachments for get requests" do
|
|
254
|
+
expect(Sdk4me::Attachments).not_to receive(:new)
|
|
255
|
+
stub_request(:get, 'https://api.4me.com/v1/requests/777?attachments=/tmp/first.png,/tmp/second.zip¬e=note').with(basic_auth: ['secret', 'x']).to_return(body: {id: 777, upload_called: false}.to_json)
|
|
256
|
+
|
|
257
|
+
response = @client.get('/requests/777', {note: 'note', attachments: ['/tmp/first.png', '/tmp/second.zip'] })
|
|
258
|
+
expect(response.valid?).to be_truthy
|
|
259
|
+
expect(response[:upload_called]).to be_falsey
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
[:post, :patch].each do |method|
|
|
263
|
+
it "should parse attachments for #{method} requests" do
|
|
264
|
+
attachments = double('Sdk4me::Attachments')
|
|
265
|
+
expect(attachments).to receive(:upload_attachments!) do |path, data|
|
|
266
|
+
expect(path).to eq '/requests/777'
|
|
267
|
+
expect(data[:attachments]).to eq ['/tmp/first.png', '/tmp/second.zip']
|
|
268
|
+
data.delete(:attachments)
|
|
269
|
+
data[:note_attachments] = 'processed'
|
|
270
|
+
end
|
|
271
|
+
expect(Sdk4me::Attachments).to receive(:new).with(@client){ attachments }
|
|
272
|
+
stub_request(method, 'https://api.4me.com/v1/requests/777').with(basic_auth: ['secret', 'x']).with(body: {note: 'note', note_attachments: 'processed' }).to_return(body: {id: 777, upload_called: true}.to_json)
|
|
273
|
+
|
|
274
|
+
response = @client.send(method, '/requests/777', {note: 'note', attachments: ['/tmp/first.png', '/tmp/second.zip'] })
|
|
275
|
+
expect(response.valid?).to be_truthy
|
|
276
|
+
expect(response[:upload_called]).to be_truthy
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
context 'import' do
|
|
283
|
+
before(:each) do
|
|
284
|
+
@client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
285
|
+
@multi_part_body = "--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"type\"\r\n\r\npeople\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{@fixture_dir}/people.csv\"\r\nContent-Type: text/csv\r\n\r\nPrimary Email,Name\nchess.cole@example.com,Chess Cole\ned.turner@example.com,Ed Turner\r\n--0123456789ABLEWASIEREISAWELBA9876543210--"
|
|
286
|
+
@multi_part_headers = {'Accept'=>'*/*', 'Content-Type'=>'multipart/form-data; boundary=0123456789ABLEWASIEREISAWELBA9876543210', 'User-Agent'=>'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6'}
|
|
287
|
+
|
|
288
|
+
@import_queued_response = {body: {state: 'queued'}.to_json}
|
|
289
|
+
@import_processing_response = {body: {state: 'processing'}.to_json}
|
|
290
|
+
@import_done_response = {body: {state: 'done', results: {errors: 0, updated: 1, created: 1, failures: 0, unchanged: 0, deleted: 0}}.to_json}
|
|
291
|
+
@import_failed_response = {body: {state: 'error', message: 'Invalid byte sequence in UTF-8 on line 2', results: {errors: 1, updated: 1, created: 0, failures: 1, unchanged: 0, deleted: 0}}.to_json}
|
|
292
|
+
allow(@client).to receive(:sleep)
|
|
293
|
+
WebMock.disable_net_connect!
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it 'should import a CSV file' do
|
|
297
|
+
stub_request(:post, 'https://api.4me.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
|
298
|
+
expect_log("Import file '#{@fixture_dir}/people.csv' successfully uploaded with token '68ef5ef0f64c0'.")
|
|
299
|
+
|
|
300
|
+
response = @client.import(File.new("#{@fixture_dir}/people.csv"), 'people')
|
|
301
|
+
expect(response[:token]).to eq('68ef5ef0f64c0')
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it 'should import a CSV file by filename' do
|
|
305
|
+
stub_request(:post, 'https://api.4me.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
|
306
|
+
response = @client.import("#{@fixture_dir}/people.csv", 'people')
|
|
307
|
+
expect(response[:token]).to eq('68ef5ef0f64c0')
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it 'should wait for the import to complete' do
|
|
311
|
+
stub_request(:post, 'https://api.4me.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
|
312
|
+
progress_stub = stub_request(:get, 'https://api.4me.com/v1/import/68ef5ef0f64c0').with(basic_auth: ['secret', 'x'])
|
|
313
|
+
.to_return(@import_queued_response, @import_processing_response)
|
|
314
|
+
.then.to_raise(StandardError.new('network error'))
|
|
315
|
+
.then.to_return(@import_done_response)
|
|
316
|
+
|
|
317
|
+
# verify the correct log statement are made
|
|
318
|
+
expect_log('Sending POST request to api.4me.com:443/v1/import', :debug)
|
|
319
|
+
expect_log("Response:\n{\n \"token\": \"68ef5ef0f64c0\"\n}", :debug)
|
|
320
|
+
expect_log("Import file '#{@fixture_dir}/people.csv' successfully uploaded with token '68ef5ef0f64c0'.")
|
|
321
|
+
expect_log('Sending GET request to api.4me.com:443/v1/import/68ef5ef0f64c0', :debug)
|
|
322
|
+
expect_log("Response:\n{\n \"state\": \"queued\"\n}", :debug)
|
|
323
|
+
expect_log("Import of '#{@fixture_dir}/people.csv' is queued. Checking again in 30 seconds.", :debug)
|
|
324
|
+
expect_log('Sending GET request to api.4me.com:443/v1/import/68ef5ef0f64c0', :debug)
|
|
325
|
+
expect_log("Response:\n{\n \"state\": \"processing\"\n}", :debug)
|
|
326
|
+
expect_log("Import of '#{@fixture_dir}/people.csv' is processing. Checking again in 30 seconds.", :debug)
|
|
327
|
+
expect_log('Sending GET request to api.4me.com:443/v1/import/68ef5ef0f64c0', :debug)
|
|
328
|
+
expect_log("Request failed: 500: No Response from Server - network error for 'api.4me.com:443/v1/import/68ef5ef0f64c0'", :error)
|
|
329
|
+
expect_log('Sending GET request to api.4me.com:443/v1/import/68ef5ef0f64c0', :debug)
|
|
330
|
+
expect_log("Response:\n{\n \"state\": \"done\",\n \"results\": {\n \"errors\": 0,\n \"updated\": 1,\n \"created\": 1,\n \"failures\": 0,\n \"unchanged\": 0,\n \"deleted\": 0\n }\n}", :debug)
|
|
331
|
+
|
|
332
|
+
response = @client.import("#{@fixture_dir}/people.csv", 'people', true)
|
|
333
|
+
expect(response[:state]).to eq('done')
|
|
334
|
+
expect(response[:results][:updated]).to eq(1)
|
|
335
|
+
expect(progress_stub).to have_been_requested.times(4)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
it 'should wait for the import to fail' do
|
|
339
|
+
stub_request(:post, 'https://api.4me.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
|
340
|
+
progress_stub = stub_request(:get, 'https://api.4me.com/v1/import/68ef5ef0f64c0').with(basic_auth: ['secret', 'x']).to_return(@import_queued_response, @import_processing_response, @import_failed_response)
|
|
341
|
+
|
|
342
|
+
expect{ @client.import("#{@fixture_dir}/people.csv", 'people', true) }.to raise_error(Sdk4me::Exception, "Unable to monitor progress for people import. Invalid byte sequence in UTF-8 on line 2")
|
|
343
|
+
expect(progress_stub).to have_been_requested.times(4)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it 'should not continue when there is an error connecting to 4me' do
|
|
347
|
+
stub_request(:post, 'https://api.4me.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
|
348
|
+
progress_stub = stub_request(:get, 'https://api.4me.com/v1/import/68ef5ef0f64c0').with(basic_auth: ['secret', 'x'])
|
|
349
|
+
.to_return(@import_queued_response, @import_processing_response)
|
|
350
|
+
.then.to_raise(StandardError.new('network error')) # twice
|
|
351
|
+
|
|
352
|
+
expect{ @client.import("#{@fixture_dir}/people.csv", 'people', true) }.to raise_error(Sdk4me::Exception, "Unable to monitor progress for people import. 500: No Response from Server - network error for 'api.4me.com:443/v1/import/68ef5ef0f64c0'")
|
|
353
|
+
expect(progress_stub).to have_been_requested.times(4)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
it 'should return an invalid response in case waiting for progress is false' do
|
|
357
|
+
stub_request(:post, 'https://api.4me.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
|
|
358
|
+
response = @client.import("#{@fixture_dir}/people.csv", 'people', false)
|
|
359
|
+
expect(response.valid?).to be_falsey
|
|
360
|
+
expect(response.message).to eq('oops!')
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
it 'should raise an UploadFailed exception in case waiting for progress is true' do
|
|
364
|
+
stub_request(:post, 'https://api.4me.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
|
|
365
|
+
expect{ @client.import("#{@fixture_dir}/people.csv", 'people', true) }.to raise_error(Sdk4me::UploadFailed, 'Failed to queue people import. oops!')
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
context 'export' do
|
|
372
|
+
before(:each) do
|
|
373
|
+
@client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
374
|
+
|
|
375
|
+
@export_queued_response = {body: {state: 'queued'}.to_json}
|
|
376
|
+
@export_processing_response = {body: {state: 'processing'}.to_json}
|
|
377
|
+
@export_done_response = {body: {state: 'done', url: 'https://download.example.com/export.zip?AWSAccessKeyId=12345'}.to_json}
|
|
378
|
+
allow(@client).to receive(:sleep)
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
it 'should export multiple types' do
|
|
382
|
+
stub_request(:post, 'https://api.4me.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people,people_contact_details'}).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
|
383
|
+
expect_log("Export for 'people,people_contact_details' successfully queued with token '68ef5ef0f64c0'.")
|
|
384
|
+
|
|
385
|
+
response = @client.export(['people', 'people_contact_details'])
|
|
386
|
+
expect(response[:token]).to eq('68ef5ef0f64c0')
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
it 'should indicate when nothing is exported' do
|
|
390
|
+
stub_request(:post, 'https://api.4me.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people', from: '2012-03-30T23:00:00+00:00'}).to_return(status: 204)
|
|
391
|
+
expect_log("No changed records for 'people' since 2012-03-30T23:00:00+00:00.")
|
|
392
|
+
|
|
393
|
+
response = @client.export('people', DateTime.new(2012,03,30,23,00,00))
|
|
394
|
+
expect(response[:token]).to be_nil
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
it 'should export since a certain time' do
|
|
398
|
+
stub_request(:post, 'https://api.4me.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people', from: '2012-03-30T23:00:00+00:00'}).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
|
399
|
+
expect_log("Export for 'people' successfully queued with token '68ef5ef0f64c0'.")
|
|
400
|
+
|
|
401
|
+
response = @client.export('people', DateTime.new(2012,03,30,23,00,00))
|
|
402
|
+
expect(response[:token]).to eq('68ef5ef0f64c0')
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
it 'should wait for the export to complete' do
|
|
406
|
+
stub_request(:post, 'https://api.4me.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people'}).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
|
407
|
+
progress_stub = stub_request(:get, 'https://api.4me.com/v1/export/68ef5ef0f64c0').with(basic_auth: ['secret', 'x'])
|
|
408
|
+
.to_return(@export_queued_response, @export_processing_response)
|
|
409
|
+
.then.to_raise(StandardError.new('network error'))
|
|
410
|
+
.then.to_return(@export_done_response)
|
|
411
|
+
|
|
412
|
+
# verify the correct log statement are made
|
|
413
|
+
expect_log('Sending POST request to api.4me.com:443/v1/export', :debug)
|
|
414
|
+
expect_log(%(Response:\n{\n "token": "68ef5ef0f64c0"\n}), :debug)
|
|
415
|
+
expect_log("Export for 'people' successfully queued with token '68ef5ef0f64c0'.")
|
|
416
|
+
expect_log('Sending GET request to api.4me.com:443/v1/export/68ef5ef0f64c0', :debug)
|
|
417
|
+
expect_log(%(Response:\n{\n "state": "queued"\n}), :debug)
|
|
418
|
+
expect_log("Export of 'people' is queued. Checking again in 30 seconds.", :debug)
|
|
419
|
+
expect_log('Sending GET request to api.4me.com:443/v1/export/68ef5ef0f64c0', :debug)
|
|
420
|
+
expect_log(%(Response:\n{\n "state": "processing"\n}), :debug)
|
|
421
|
+
expect_log("Export of 'people' is processing. Checking again in 30 seconds.", :debug)
|
|
422
|
+
expect_log('Sending GET request to api.4me.com:443/v1/export/68ef5ef0f64c0', :debug)
|
|
423
|
+
expect_log("Request failed: 500: No Response from Server - network error for 'api.4me.com:443/v1/export/68ef5ef0f64c0'", :error)
|
|
424
|
+
expect_log('Sending GET request to api.4me.com:443/v1/export/68ef5ef0f64c0', :debug)
|
|
425
|
+
expect_log(%(Response:\n{\n "state": "done",\n "url": "https://download.example.com/export.zip?AWSAccessKeyId=12345"\n}), :debug)
|
|
426
|
+
|
|
427
|
+
response = @client.export('people', nil, true)
|
|
428
|
+
expect(response[:state]).to eq('done')
|
|
429
|
+
expect(response[:url]).to eq('https://download.example.com/export.zip?AWSAccessKeyId=12345')
|
|
430
|
+
expect(progress_stub).to have_been_requested.times(4)
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
it 'should not continue when there is an error connecting to 4me' do
|
|
434
|
+
stub_request(:post, 'https://api.4me.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people'}).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
|
435
|
+
progress_stub = stub_request(:get, 'https://api.4me.com/v1/export/68ef5ef0f64c0').with(basic_auth: ['secret', 'x'])
|
|
436
|
+
.to_return(@export_queued_response, @export_processing_response)
|
|
437
|
+
.then.to_raise(StandardError.new('network error')) # twice
|
|
438
|
+
|
|
439
|
+
expect{ @client.export('people', nil, true) }.to raise_error(Sdk4me::Exception, "Unable to monitor progress for 'people' export. 500: No Response from Server - network error for 'api.4me.com:443/v1/export/68ef5ef0f64c0'")
|
|
440
|
+
expect(progress_stub).to have_been_requested.times(4)
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
it 'should return an invalid response in case waiting for progress is false' do
|
|
444
|
+
stub_request(:post, 'https://api.4me.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people'}).to_return(body: {message: 'oops!'}.to_json)
|
|
445
|
+
response = @client.export('people')
|
|
446
|
+
expect(response.valid?).to be_falsey
|
|
447
|
+
expect(response.message).to eq('oops!')
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
it 'should raise an UploadFailed exception in case waiting for progress is true' do
|
|
451
|
+
stub_request(:post, 'https://api.4me.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people'}).to_return(body: {message: 'oops!'}.to_json)
|
|
452
|
+
expect{ @client.export('people', nil, true) }.to raise_error(Sdk4me::UploadFailed, "Failed to queue 'people' export. oops!")
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
context 'retry' do
|
|
458
|
+
it 'should not retry when max_retry_time = -1' do
|
|
459
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).to_raise(StandardError.new('network error'))
|
|
460
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug )
|
|
461
|
+
expect_log("Request failed: 500: No Response from Server - network error for 'api.4me.com:443/v1/me'", :error)
|
|
462
|
+
|
|
463
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
|
|
464
|
+
response = client.get('me')
|
|
465
|
+
expect(stub).to have_been_requested.times(1)
|
|
466
|
+
expect(response.valid?).to be_falsey
|
|
467
|
+
expect(response.message).to eq("500: No Response from Server - network error for 'api.4me.com:443/v1/me'")
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
it 'should not retry 4 times when max_retry_time = 16' do
|
|
471
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).to_raise(StandardError.new('network error'))
|
|
472
|
+
[2,4,8,16].each_with_index do |secs, i|
|
|
473
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug )
|
|
474
|
+
expect_log("Request failed, retry ##{i+1} in #{secs} seconds: 500: No Response from Server - network error for 'api.4me.com:443/v1/me'", :warn)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: 16)
|
|
478
|
+
allow(client).to receive(:sleep)
|
|
479
|
+
response = client.get('me')
|
|
480
|
+
expect(stub).to have_been_requested.times(4)
|
|
481
|
+
expect(response.valid?).to be_falsey
|
|
482
|
+
expect(response.message).to eq("500: No Response from Server - network error for 'api.4me.com:443/v1/me'")
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
it 'should return the response after retry succeeds' do
|
|
486
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).to_raise(StandardError.new('network error')).then.to_return(body: {name: 'my name'}.to_json)
|
|
487
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug )
|
|
488
|
+
expect_log("Request failed, retry #1 in 2 seconds: 500: No Response from Server - network error for 'api.4me.com:443/v1/me'", :warn)
|
|
489
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug )
|
|
490
|
+
expect_log(%(Response:\n{\n "name": "my name"\n}), :debug )
|
|
491
|
+
|
|
492
|
+
client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: 16)
|
|
493
|
+
allow(client).to receive(:sleep)
|
|
494
|
+
response = client.get('me')
|
|
495
|
+
expect(stub).to have_been_requested.times(2)
|
|
496
|
+
expect(response.valid?).to be_truthy
|
|
497
|
+
expect(response[:name]).to eq('my name')
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
context 'rate limiting' do
|
|
502
|
+
it 'should not block on rate limit when block_at_rate_limit is false' do
|
|
503
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(status: 429, body: {message: 'Too Many Requests'}.to_json)
|
|
504
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug )
|
|
505
|
+
expect_log("Request failed: 429: Too Many Requests", :error)
|
|
506
|
+
|
|
507
|
+
client = Sdk4me::Client.new(api_token: 'secret', block_at_rate_limit: false)
|
|
508
|
+
response = client.get('me')
|
|
509
|
+
expect(stub).to have_been_requested.times(1)
|
|
510
|
+
expect(response.valid?).to be_falsey
|
|
511
|
+
expect(response.message).to eq('429: Too Many Requests')
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
it 'should block on rate limit when block_at_rate_limit is true' do
|
|
515
|
+
stub = stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(status: 429, body: {message: 'Too Many Requests'}.to_json).then.to_return(body: {name: 'my name'}.to_json)
|
|
516
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug )
|
|
517
|
+
expect_log('Request throttled, trying again in 5 minutes: 429: Too Many Requests', :warn)
|
|
518
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug )
|
|
519
|
+
expect_log(%(Response:\n{\n "name": "my name"\n}), :debug )
|
|
520
|
+
|
|
521
|
+
client = Sdk4me::Client.new(api_token: 'secret', block_at_rate_limit: true)
|
|
522
|
+
allow(client).to receive(:sleep)
|
|
523
|
+
response = client.get('me')
|
|
524
|
+
expect(stub).to have_been_requested.times(2)
|
|
525
|
+
expect(response.valid?).to be_truthy
|
|
526
|
+
expect(response[:name]).to eq('my name')
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
context 'logger' do
|
|
531
|
+
before(:each) do
|
|
532
|
+
@logger = Logger.new(STDOUT)
|
|
533
|
+
@client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1, logger: @logger)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
it 'should be possible to override the default logger' do
|
|
537
|
+
stub_request(:get, 'https://api.4me.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(body: {name: 'my name'}.to_json)
|
|
538
|
+
expect_log('Sending GET request to api.4me.com:443/v1/me', :debug, @logger )
|
|
539
|
+
expect_log(%(Response:\n{\n "name": "my name"\n}), :debug, @logger )
|
|
540
|
+
@client.get('me')
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
end
|