itrp-client 1.0.14 → 1.1.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 +4 -4
- data/Gemfile.lock +15 -13
- data/LICENSE.txt +1 -1
- data/itrp-client.gemspec +2 -2
- data/lib/itrp/client.rb +22 -18
- data/lib/itrp/client/version.rb +1 -1
- data/spec/lib/itrp/attachments_spec.rb +178 -0
- data/spec/lib/itrp/certificate_spec.rb +28 -0
- data/spec/lib/itrp/client_spec.rb +527 -0
- data/spec/lib/itrp/response_spec.rb +271 -0
- data/spec/lib/itrp_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 +46 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89081124a0ce058991107658acc89152c8d4118e
|
4
|
+
data.tar.gz: b92af38225ebdbd0592f508b4e5ddd7fb6c6983c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 232eeb5ef89f2e2f6c83266eb95a777ecccfa891e9b7fed8ea88c51c332183c7ffec3aaaed8eb259c66a540a0d86d0152fb65612aab779f463cd8ad7b24f6249
|
7
|
+
data.tar.gz: acc1b94b7a6ea21c695a0f44ef3d332a601f0ac3c77af43499c79cb386424aa0958498a5d2cb62b4184f83eb96b04356b99129b3f693f81663767db02e257cf4
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
itrp-client (1.0
|
4
|
+
itrp-client (1.1.0)
|
5
5
|
activesupport
|
6
6
|
gem_config
|
7
7
|
mime-types
|
@@ -9,26 +9,28 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
-
activesupport (4.2.
|
12
|
+
activesupport (4.2.7.1)
|
13
13
|
i18n (~> 0.7)
|
14
14
|
json (~> 1.7, >= 1.7.7)
|
15
15
|
minitest (~> 5.1)
|
16
16
|
thread_safe (~> 0.3, >= 0.3.4)
|
17
17
|
tzinfo (~> 1.1)
|
18
|
-
addressable (2.
|
18
|
+
addressable (2.5.0)
|
19
|
+
public_suffix (~> 2.0, >= 2.0.2)
|
19
20
|
crack (0.4.3)
|
20
21
|
safe_yaml (~> 1.0.0)
|
21
22
|
diff-lcs (1.2.5)
|
22
23
|
docile (1.1.5)
|
23
24
|
gem_config (0.3.1)
|
24
|
-
hashdiff (0.
|
25
|
+
hashdiff (0.3.1)
|
25
26
|
i18n (0.7.0)
|
26
27
|
json (1.8.3)
|
27
|
-
mime-types (3.
|
28
|
+
mime-types (3.1)
|
28
29
|
mime-types-data (~> 3.2015)
|
29
|
-
mime-types-data (3.
|
30
|
-
minitest (5.
|
31
|
-
|
30
|
+
mime-types-data (3.2016.0521)
|
31
|
+
minitest (5.9.1)
|
32
|
+
public_suffix (2.0.4)
|
33
|
+
rake (11.3.0)
|
32
34
|
rspec (3.3.0)
|
33
35
|
rspec-core (~> 3.3.0)
|
34
36
|
rspec-expectations (~> 3.3.0)
|
@@ -43,15 +45,15 @@ GEM
|
|
43
45
|
rspec-support (~> 3.3.0)
|
44
46
|
rspec-support (3.3.0)
|
45
47
|
safe_yaml (1.0.4)
|
46
|
-
simplecov (0.
|
48
|
+
simplecov (0.12.0)
|
47
49
|
docile (~> 1.1.0)
|
48
|
-
json (
|
50
|
+
json (>= 1.8, < 3)
|
49
51
|
simplecov-html (~> 0.10.0)
|
50
52
|
simplecov-html (0.10.0)
|
51
53
|
thread_safe (0.3.5)
|
52
54
|
tzinfo (1.2.2)
|
53
55
|
thread_safe (~> 0.1)
|
54
|
-
webmock (1.
|
56
|
+
webmock (2.1.0)
|
55
57
|
addressable (>= 2.3.6)
|
56
58
|
crack (>= 0.3.2)
|
57
59
|
hashdiff
|
@@ -65,7 +67,7 @@ DEPENDENCIES
|
|
65
67
|
rake
|
66
68
|
rspec (~> 3.3.0)
|
67
69
|
simplecov
|
68
|
-
webmock
|
70
|
+
webmock (~> 2)
|
69
71
|
|
70
72
|
BUNDLED WITH
|
71
|
-
1.
|
73
|
+
1.11.2
|
data/LICENSE.txt
CHANGED
data/itrp-client.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "itrp-client"
|
8
8
|
spec.version = Itrp::Client::VERSION
|
9
9
|
spec.platform = Gem::Platform::RUBY
|
10
|
-
spec.required_ruby_version = '>=
|
10
|
+
spec.required_ruby_version = '>= 2.0.0'
|
11
11
|
spec.authors = ["ITRP"]
|
12
12
|
spec.email = %q{developers@itrp.com}
|
13
13
|
spec.description = %q{Client for accessing the ITRP REST API}
|
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency "bundler", "~> 1.3"
|
35
35
|
spec.add_development_dependency "rake"
|
36
36
|
spec.add_development_dependency 'rspec', "~> 3.3.0"
|
37
|
-
spec.add_development_dependency 'webmock'
|
37
|
+
spec.add_development_dependency 'webmock', "~> 2"
|
38
38
|
spec.add_development_dependency 'simplecov'
|
39
39
|
|
40
40
|
end
|
data/lib/itrp/client.rb
CHANGED
@@ -225,7 +225,7 @@ module Itrp
|
|
225
225
|
path
|
226
226
|
end
|
227
227
|
|
228
|
-
# Expand one parameter, e.g. (:"created_at=>", DateTime.now) to "created_at=%3E22011-12-16T12:24:41
|
228
|
+
# Expand one parameter, e.g. (:"created_at=>", DateTime.now) to "created_at=%3E22011-12-16T12:24:41%2B01:00"
|
229
229
|
def expand_param(key, value)
|
230
230
|
param = uri_escape(key.to_s).gsub('%3D', '=') # handle :"updated_at=>" or :"person_id!=" parameters
|
231
231
|
param << '=' unless key['=']
|
@@ -237,10 +237,10 @@ module Itrp
|
|
237
237
|
def typecast(value, escape = true)
|
238
238
|
case value.class.name.to_sym
|
239
239
|
when :NilClass then ''
|
240
|
-
when :String then escape ?
|
240
|
+
when :String then escape ? uri_escape(value) : value
|
241
241
|
when :TrueClass then 'true'
|
242
242
|
when :FalseClass then 'false'
|
243
|
-
when :DateTime then value.new_offset(0).iso8601
|
243
|
+
when :DateTime then datetime = value.new_offset(0).iso8601; escape ? uri_escape(datetime) : datetime
|
244
244
|
when :Date then value.strftime("%Y-%m-%d")
|
245
245
|
when :Time then value.strftime("%H:%M")
|
246
246
|
# do not convert arrays in put/post requests as squashing arrays is only used in filtering
|
@@ -278,41 +278,45 @@ module Itrp
|
|
278
278
|
response
|
279
279
|
end
|
280
280
|
|
281
|
+
# parse the given URI to [domain, port, ssl, path]
|
282
|
+
def ssl_domain_port_path(uri)
|
283
|
+
uri = URI.parse(uri)
|
284
|
+
ssl = uri.scheme == 'https'
|
285
|
+
[ssl, uri.host, uri.port, uri.path]
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
module SendWithRateLimitBlock
|
281
291
|
# Wraps the _send method with retries when the server does not responsd, see +initialize+ option +:rate_limit_block+
|
282
|
-
def
|
283
|
-
return
|
292
|
+
def _send(request, domain = @domain, port = @port, ssl = @ssl)
|
293
|
+
return super(request, domain, port, ssl) unless option(:block_at_rate_limit)
|
284
294
|
now = Time.now
|
285
295
|
begin
|
286
|
-
_response =
|
296
|
+
_response = super(request, domain, port, ssl)
|
287
297
|
@logger.warn { "Request throttled, trying again in 5 minutes: #{_response.message}" } and sleep(300) if _response.throttled?
|
288
298
|
end while _response.throttled? && (Time.now - now) < 3660 # max 1 hour and 1 minute
|
289
299
|
_response
|
290
300
|
end
|
291
|
-
|
301
|
+
end
|
302
|
+
Client.send(:prepend, SendWithRateLimitBlock)
|
292
303
|
|
304
|
+
module SendWithRetries
|
293
305
|
# Wraps the _send method with retries when the server does not responsd, see +initialize+ option +:retries+
|
294
|
-
def
|
306
|
+
def _send(request, domain = @domain, port = @port, ssl = @ssl)
|
295
307
|
retries = 0
|
296
308
|
sleep_time = 2
|
297
309
|
total_retry_time = 0
|
298
310
|
begin
|
299
|
-
_response =
|
311
|
+
_response = super(request, domain, port, ssl)
|
300
312
|
@logger.warn { "Request failed, retry ##{retries += 1} in #{sleep_time} seconds: #{_response.message}" } and sleep(sleep_time) if _response.empty? && option(:max_retry_time) > 0
|
301
313
|
total_retry_time += sleep_time
|
302
314
|
sleep_time *= 2
|
303
315
|
end while _response.empty? && total_retry_time < option(:max_retry_time)
|
304
316
|
_response
|
305
317
|
end
|
306
|
-
alias_method_chain :_send, :retries
|
307
|
-
|
308
|
-
# parse the given URI to [domain, port, ssl, path]
|
309
|
-
def ssl_domain_port_path(uri)
|
310
|
-
uri = URI.parse(uri)
|
311
|
-
ssl = uri.scheme == 'https'
|
312
|
-
[ssl, uri.host, uri.port, uri.path]
|
313
|
-
end
|
314
|
-
|
315
318
|
end
|
319
|
+
Client.send(:prepend, SendWithRetries)
|
316
320
|
end
|
317
321
|
|
318
322
|
# HTTPS with certificate bundle
|
data/lib/itrp/client/version.rb
CHANGED
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Itrp::Attachments do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
7
|
+
@attachments = Itrp::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.itrp.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.itrp.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(::Itrp::UploadFailed, message)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should add /new to the path for new records' do
|
37
|
+
stub_request(:get, 'https://api.itrp.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.itrp.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.itrp.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(::Itrp::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.itrp.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.itrp.com/s3_success?sig=99e82e8a046'})
|
113
|
+
stub_request(:get, "https://api.itrp.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.itrp.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 ITRP 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.itrp.com/s3_success?sig=99e82e8a046'})
|
125
|
+
stub_request(:get, "https://api.itrp.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: ITRP 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.itrp.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(::Itrp::UploadFailed, message)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'itrp' do
|
139
|
+
before(:each) do
|
140
|
+
@itrp_conf = {
|
141
|
+
provider: 'local',
|
142
|
+
upload_uri: 'https://api.itrp.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(:itrp_upload).with(@itrp_conf, @key_template, @key, kind_of(File))
|
154
|
+
expect(@attachments.send(:upload_attachment, @itrp_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should sent the upload to ITRP' do
|
158
|
+
stub_request(:post, 'https://api.itrp.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, @itrp_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should report an error when ITRP upload fails' do
|
163
|
+
stub_request(:post, 'https://api.itrp.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: ITRP upload to https://api.itrp.com/attachments for #{@key} failed: oops!", :error)
|
166
|
+
expect(@attachments.send(:upload_attachment, @itrp_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'should raise an exception when ITRP upload fails' do
|
170
|
+
stub_request(:post, 'https://api.itrp.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: ITRP upload to https://api.itrp.com/attachments for #{@key} failed: oops!"
|
173
|
+
expect{ @attachments.send(:upload_attachment, @itrp_conf, "#{@fixture_dir}/upload.txt", true) }.to raise_error(::Itrp::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 ITRP API' do
|
6
|
+
WebMock.allow_net_connect!
|
7
|
+
client = Itrp::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('itrp-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,527 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Itrp::Client do
|
4
|
+
|
5
|
+
context 'Itrp.config' do
|
6
|
+
before(:each) do
|
7
|
+
Itrp.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(Itrp::Client::MAX_PAGE_SIZE).to eq(100)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should use the Itrp configuration' do
|
18
|
+
client = Itrp::Client.new
|
19
|
+
expect(client.option(:host)).to eq('https://api.itrp.com') # default value
|
20
|
+
expect(client.option(:api_token)).to eq('secret') # value set using Itrp.config
|
21
|
+
expect(client.option(:max_retry_time)).to eq(120) # value overridden in Itrp.config
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should override the Itrp configuration' do
|
25
|
+
client = Itrp::Client.new(host: 'https://demo.itrp.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.itrp.com') # default value overridden in Client.new
|
28
|
+
expect(client.option(:api_token)).to eq('unknown') # value set using Itrp.config and overridden in Client.new
|
29
|
+
expect(client.option(:max_retry_time)).to eq(120) # value overridden in Itrp.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 { Itrp::Client.new(required_option => '') }.to raise_error("Missing required configuration option #{required_option}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
[ ['https://api.itrp.com', true, 'api.itrp.com', 443],
|
40
|
+
['https://api.example.com:777', true, 'api.example.com', 777],
|
41
|
+
['http://itrp.example.com', false, 'itrp.example.com', 80],
|
42
|
+
['http://itrp.example.com:777', false, 'itrp.example.com', 777]
|
43
|
+
].each do |host, ssl, domain, port|
|
44
|
+
it 'should parse ssl, host and port' do
|
45
|
+
client = Itrp::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.itrp.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 = Itrp::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.itrp.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-ITRP-Account header' do
|
74
|
+
client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1, account: 'test')
|
75
|
+
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).with(headers: {'X-ITRP-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-ITRP-Source header' do
|
81
|
+
client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1, source: 'myapp')
|
82
|
+
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).with(headers: {'X-ITRP-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.itrp.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.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).with(headers: {'X-ITRP-Other' => 'value'}).to_return(body: {name: 'my name'}.to_json)
|
95
|
+
@client.get('me', {}, {'X-ITRP-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.itrp.com/v1/requests?fields=subject&page=1&per_page=100').with(basic_auth: ['secret', 'x']).with(headers: {'X-ITRP-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-ITRP-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 = Itrp::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.itrp.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.itrp.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.itrp.com/v1/requests?page=1&per_page=2>; rel="first",<https://api.itrp.com/v1/requests?page=2&per_page=2>; rel="next",<https://api.itrp.com/v1/requests?page=2&per_page=2>; rel="last"'})
|
123
|
+
stub_page2 = stub_request(:get, 'https://api.itrp.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.itrp.com/v1/requests?page=1&per_page=2>; rel="first",<https://api.itrp.com/v1/requests?page=1&per_page=2>; rel="prev",<https://api.itrp.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 = Itrp::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.itrp.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.itrp.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 = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
164
|
+
stub = stub_request(:post, 'https://api.itrp.com/v1/people').with(basic_auth: ['secret', 'x']).with(body: {user_ids: [1, 2, 3]}, headers: {'X-ITRP-Custom' => 'custom'}).to_return(body: {id: 101}.to_json)
|
165
|
+
client.post('people', {user_ids: [1, 2, 3]}, {'X-ITRP-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 = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
171
|
+
stub = stub_request(:patch, 'https://api.itrp.com/v1/people/55').with(basic_auth: ['secret', 'x']).with(body: '{"contacts_attributes":{"0":{"protocol":"email","label":"work","uri":"work@example.com"}}}', headers: {'X-ITRP-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-ITRP-Custom' => 'custom'})
|
173
|
+
expect(stub).to have_been_requested
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should not double escape symbols' do
|
177
|
+
client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
178
|
+
stub = stub_request(:patch, 'https://api.itrp.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.itrp.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.itrp.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 = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
202
|
+
stub = stub_request(:patch, 'https://api.itrp.com/v1/people/1').with(basic_auth: ['secret', 'x']).with(body: {name: 'New Name'}, headers: {'X-ITRP-Custom' => 'custom'}).to_return(body: {id: 1}.to_json)
|
203
|
+
client.send(method, 'people/1', {name: 'New Name'}, {'X-ITRP-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 = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
212
|
+
stub = stub_request(:post, 'https://api.itrp.com/v1/people').with(basic_auth: ['secret', 'x']).with(body: {name: 'New Name'}, headers: {'X-ITRP-Custom' => 'custom'}).to_return(body: {id: 101}.to_json)
|
213
|
+
client.post('people', {name: 'New Name'}, {'X-ITRP-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 = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
221
|
+
stub = stub_request(:delete, 'https://api.itrp.com/v1/people?id=value').with(basic_auth: ['secret', 'x']).with(headers: {'X-ITRP-Custom' => 'custom'}).to_return(body: {id: 101}.to_json)
|
222
|
+
client.delete('people', {id: 'value'}, {'X-ITRP-Custom' => 'custom'})
|
223
|
+
expect(stub).to have_been_requested
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context 'attachments' do
|
228
|
+
before(:each) do
|
229
|
+
@client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'should not log an error for XML responses' do
|
233
|
+
xml = %(<?xml version="1.0" encoding="UTF-8"?>\n<details>some info</details>)
|
234
|
+
stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(body: xml)
|
235
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug)
|
236
|
+
expect_log("XML response:\n#{xml}", :debug)
|
237
|
+
response = @client.get('me')
|
238
|
+
expect(response.valid?).to be_falsey
|
239
|
+
expect(response.raw.body).to eq(xml)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'should not log an error for redirects' do
|
243
|
+
stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(body: '', status: 303, headers: {'Location' => 'http://redirect.example.com/to/here'})
|
244
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug)
|
245
|
+
expect_log('Redirect: http://redirect.example.com/to/here', :debug)
|
246
|
+
response = @client.get('me')
|
247
|
+
expect(response.valid?).to be_falsey
|
248
|
+
expect(response.raw.body).to be_nil
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should not parse attachments for get requests" do
|
252
|
+
expect(Itrp::Attachments).not_to receive(:new)
|
253
|
+
stub_request(:get, 'https://api.itrp.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)
|
254
|
+
|
255
|
+
response = @client.get('/requests/777', {note: 'note', attachments: ['/tmp/first.png', '/tmp/second.zip'] })
|
256
|
+
expect(response.valid?).to be_truthy
|
257
|
+
expect(response[:upload_called]).to be_falsey
|
258
|
+
end
|
259
|
+
|
260
|
+
[:post, :patch].each do |method|
|
261
|
+
it "should parse attachments for #{method} requests" do
|
262
|
+
attachments = double('Itrp::Attachments')
|
263
|
+
expect(attachments).to receive(:upload_attachments!) do |path, data|
|
264
|
+
expect(path).to eq '/requests/777'
|
265
|
+
expect(data[:attachments]).to eq ['/tmp/first.png', '/tmp/second.zip']
|
266
|
+
data.delete(:attachments)
|
267
|
+
data[:note_attachments] = 'processed'
|
268
|
+
end
|
269
|
+
expect(Itrp::Attachments).to receive(:new).with(@client){ attachments }
|
270
|
+
stub_request(method, 'https://api.itrp.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)
|
271
|
+
|
272
|
+
response = @client.send(method, '/requests/777', {note: 'note', attachments: ['/tmp/first.png', '/tmp/second.zip'] })
|
273
|
+
expect(response.valid?).to be_truthy
|
274
|
+
expect(response[:upload_called]).to be_truthy
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
end
|
279
|
+
|
280
|
+
context 'import' do
|
281
|
+
before(:each) do
|
282
|
+
@client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
283
|
+
@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/comma-separated-values\r\n\r\nPrimary Email,Name\nchess.cole@example.com,Chess Cole\ned.turner@example.com,Ed Turner\r\n--0123456789ABLEWASIEREISAWELBA9876543210--"
|
284
|
+
@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'}
|
285
|
+
|
286
|
+
@import_queued_response = {body: {state: 'queued'}.to_json}
|
287
|
+
@import_processing_response = {body: {state: 'processing'}.to_json}
|
288
|
+
@import_done_response = {body: {state: 'done', results: {errors: 0, updated: 1, created: 1, failures: 0, unchanged: 0, deleted: 0}}.to_json}
|
289
|
+
@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}
|
290
|
+
@server_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}
|
291
|
+
allow(@client).to receive(:sleep)
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'should import a CSV file' do
|
295
|
+
stub_request(:post, 'https://api.itrp.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
296
|
+
expect_log("Import file '#{@fixture_dir}/people.csv' successfully uploaded with token '68ef5ef0f64c0'.")
|
297
|
+
|
298
|
+
response = @client.import(File.new("#{@fixture_dir}/people.csv"), 'people')
|
299
|
+
expect(response[:token]).to eq('68ef5ef0f64c0')
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'should import a CSV file by filename' do
|
303
|
+
stub_request(:post, 'https://api.itrp.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
304
|
+
response = @client.import("#{@fixture_dir}/people.csv", 'people')
|
305
|
+
expect(response[:token]).to eq('68ef5ef0f64c0')
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'should wait for the import to complete' do
|
309
|
+
stub_request(:post, 'https://api.itrp.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
310
|
+
progress_stub = stub_request(:get, 'https://api.itrp.com/v1/import/68ef5ef0f64c0').with(basic_auth: ['secret', 'x']).to_return(@import_queued_response, @import_processing_response, @import_done_response)
|
311
|
+
|
312
|
+
# verify the correct log statement are made
|
313
|
+
expect_log('Sending POST request to api.itrp.com:443/v1/import', :debug)
|
314
|
+
expect_log("Response:\n{\n \"token\": \"68ef5ef0f64c0\"\n}", :debug)
|
315
|
+
expect_log("Import file '#{@fixture_dir}/people.csv' successfully uploaded with token '68ef5ef0f64c0'.")
|
316
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/import/68ef5ef0f64c0', :debug)
|
317
|
+
expect_log("Response:\n{\n \"state\": \"queued\"\n}", :debug)
|
318
|
+
expect_log("Import of '#{@fixture_dir}/people.csv' is queued. Checking again in 30 seconds.", :debug)
|
319
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/import/68ef5ef0f64c0', :debug)
|
320
|
+
expect_log("Response:\n{\n \"state\": \"processing\"\n}", :debug)
|
321
|
+
expect_log("Import of '#{@fixture_dir}/people.csv' is processing. Checking again in 30 seconds.", :debug)
|
322
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/import/68ef5ef0f64c0', :debug)
|
323
|
+
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)
|
324
|
+
|
325
|
+
response = @client.import("#{@fixture_dir}/people.csv", 'people', true)
|
326
|
+
expect(response[:state]).to eq('done')
|
327
|
+
expect(response[:results][:updated]).to eq(1)
|
328
|
+
expect(progress_stub).to have_been_requested.times(3)
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'should wait for the import to fail' do
|
332
|
+
stub_request(:post, 'https://api.itrp.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
333
|
+
progress_stub = stub_request(:get, 'https://api.itrp.com/v1/import/68ef5ef0f64c0').with(basic_auth: ['secret', 'x']).to_return(@import_queued_response, @import_processing_response, @import_failed_response)
|
334
|
+
|
335
|
+
expect{ @client.import("#{@fixture_dir}/people.csv", 'people', true) }.to raise_error(Itrp::Exception, "Unable to monitor progress for people import. Invalid byte sequence in UTF-8 on line 2")
|
336
|
+
expect(progress_stub).to have_been_requested.times(3)
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'should not continue when there is an error connecting to ITRP' do
|
340
|
+
stub_request(:post, 'https://api.itrp.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
341
|
+
progress_stub = stub_request(:get, 'https://api.itrp.com/v1/import/68ef5ef0f64c0').with(basic_auth: ['secret', 'x']).to_return(@import_queued_response, @import_processing_response).then.to_raise(StandardError.new('network error'))
|
342
|
+
|
343
|
+
expect{ @client.import("#{@fixture_dir}/people.csv", 'people', true) }.to raise_error(Itrp::Exception, "Unable to monitor progress for people import. 500: No Response from Server - network error for 'api.itrp.com:443/v1/import/68ef5ef0f64c0'")
|
344
|
+
expect(progress_stub).to have_been_requested.times(3)
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'should return an invalid response in case waiting for progress is false' do
|
348
|
+
stub_request(:post, 'https://api.itrp.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
|
349
|
+
response = @client.import("#{@fixture_dir}/people.csv", 'people', false)
|
350
|
+
expect(response.valid?).to be_falsey
|
351
|
+
expect(response.message).to eq('oops!')
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'should raise an UploadFailed exception in case waiting for progress is true' do
|
355
|
+
stub_request(:post, 'https://api.itrp.com/v1/import').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
|
356
|
+
expect{ @client.import("#{@fixture_dir}/people.csv", 'people', true) }.to raise_error(Itrp::UploadFailed, 'Failed to queue people import. oops!')
|
357
|
+
end
|
358
|
+
|
359
|
+
end
|
360
|
+
|
361
|
+
|
362
|
+
context 'export' do
|
363
|
+
before(:each) do
|
364
|
+
@client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
365
|
+
|
366
|
+
@export_queued_response = {body: {state: 'queued'}.to_json}
|
367
|
+
@export_processing_response = {body: {state: 'processing'}.to_json}
|
368
|
+
@export_done_response = {body: {state: 'done', url: 'https://download.example.com/export.zip?AWSAccessKeyId=12345'}.to_json}
|
369
|
+
allow(@client).to receive(:sleep)
|
370
|
+
end
|
371
|
+
|
372
|
+
it 'should export multiple types' do
|
373
|
+
stub_request(:post, 'https://api.itrp.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people,people_contact_details'}).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
374
|
+
expect_log("Export for 'people,people_contact_details' successfully queued with token '68ef5ef0f64c0'.")
|
375
|
+
|
376
|
+
response = @client.export(['people', 'people_contact_details'])
|
377
|
+
expect(response[:token]).to eq('68ef5ef0f64c0')
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'should indicate when nothing is exported' do
|
381
|
+
stub_request(:post, 'https://api.itrp.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people', from: '2012-03-30T23:00:00+00:00'}).to_return(status: 204)
|
382
|
+
expect_log("No changed records for 'people' since 2012-03-30T23:00:00+00:00.")
|
383
|
+
|
384
|
+
response = @client.export('people', DateTime.new(2012,03,30,23,00,00))
|
385
|
+
expect(response[:token]).to be_nil
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'should export since a certain time' do
|
389
|
+
stub_request(:post, 'https://api.itrp.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)
|
390
|
+
expect_log("Export for 'people' successfully queued with token '68ef5ef0f64c0'.")
|
391
|
+
|
392
|
+
response = @client.export('people', DateTime.new(2012,03,30,23,00,00))
|
393
|
+
expect(response[:token]).to eq('68ef5ef0f64c0')
|
394
|
+
end
|
395
|
+
|
396
|
+
it 'should wait for the export to complete' do
|
397
|
+
stub_request(:post, 'https://api.itrp.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people'}).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
398
|
+
progress_stub = stub_request(:get, 'https://api.itrp.com/v1/export/68ef5ef0f64c0').with(basic_auth: ['secret', 'x']).to_return(@export_queued_response, @export_processing_response, @export_done_response)
|
399
|
+
|
400
|
+
# verify the correct log statement are made
|
401
|
+
expect_log('Sending POST request to api.itrp.com:443/v1/export', :debug)
|
402
|
+
expect_log(%(Response:\n{\n "token": "68ef5ef0f64c0"\n}), :debug)
|
403
|
+
expect_log("Export for 'people' successfully queued with token '68ef5ef0f64c0'.")
|
404
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/export/68ef5ef0f64c0', :debug)
|
405
|
+
expect_log(%(Response:\n{\n "state": "queued"\n}), :debug)
|
406
|
+
expect_log("Export of 'people' is queued. Checking again in 30 seconds.", :debug)
|
407
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/export/68ef5ef0f64c0', :debug)
|
408
|
+
expect_log(%(Response:\n{\n "state": "processing"\n}), :debug)
|
409
|
+
expect_log("Export of 'people' is processing. Checking again in 30 seconds.", :debug)
|
410
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/export/68ef5ef0f64c0', :debug)
|
411
|
+
expect_log(%(Response:\n{\n "state": "done",\n "url": "https://download.example.com/export.zip?AWSAccessKeyId=12345"\n}), :debug)
|
412
|
+
|
413
|
+
response = @client.export('people', nil, true)
|
414
|
+
expect(response[:state]).to eq('done')
|
415
|
+
expect(response[:url]).to eq('https://download.example.com/export.zip?AWSAccessKeyId=12345')
|
416
|
+
expect(progress_stub).to have_been_requested.times(3)
|
417
|
+
end
|
418
|
+
|
419
|
+
it 'should not continue when there is an error connecting to ITRP' do
|
420
|
+
stub_request(:post, 'https://api.itrp.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people'}).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
|
421
|
+
progress_stub = stub_request(:get, 'https://api.itrp.com/v1/export/68ef5ef0f64c0').with(basic_auth: ['secret', 'x']).to_return(@export_queued_response, @export_processing_response).then.to_raise(StandardError.new('network error'))
|
422
|
+
|
423
|
+
expect{ @client.export('people', nil, true) }.to raise_error(Itrp::Exception, "Unable to monitor progress for 'people' export. 500: No Response from Server - network error for 'api.itrp.com:443/v1/export/68ef5ef0f64c0'")
|
424
|
+
expect(progress_stub).to have_been_requested.times(3)
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'should return an invalid response in case waiting for progress is false' do
|
428
|
+
stub_request(:post, 'https://api.itrp.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people'}).to_return(body: {message: 'oops!'}.to_json)
|
429
|
+
response = @client.export('people')
|
430
|
+
expect(response.valid?).to be_falsey
|
431
|
+
expect(response.message).to eq('oops!')
|
432
|
+
end
|
433
|
+
|
434
|
+
it 'should raise an UploadFailed exception in case waiting for progress is true' do
|
435
|
+
stub_request(:post, 'https://api.itrp.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'people'}).to_return(body: {message: 'oops!'}.to_json)
|
436
|
+
expect{ @client.export('people', nil, true) }.to raise_error(Itrp::UploadFailed, "Failed to queue 'people' export. oops!")
|
437
|
+
end
|
438
|
+
|
439
|
+
end
|
440
|
+
|
441
|
+
context 'retry' do
|
442
|
+
it 'should not retry when max_retry_time = -1' do
|
443
|
+
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_raise(StandardError.new('network error'))
|
444
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
445
|
+
expect_log("Request failed: 500: No Response from Server - network error for 'api.itrp.com:443/v1/me'", :error)
|
446
|
+
|
447
|
+
client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
|
448
|
+
response = client.get('me')
|
449
|
+
expect(stub).to have_been_requested.times(1)
|
450
|
+
expect(response.valid?).to be_falsey
|
451
|
+
expect(response.message).to eq("500: No Response from Server - network error for 'api.itrp.com:443/v1/me'")
|
452
|
+
end
|
453
|
+
|
454
|
+
it 'should not retry 4 times when max_retry_time = 16' do
|
455
|
+
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_raise(StandardError.new('network error'))
|
456
|
+
[2,4,8,16].each_with_index do |secs, i|
|
457
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
458
|
+
expect_log("Request failed, retry ##{i+1} in #{secs} seconds: 500: No Response from Server - network error for 'api.itrp.com:443/v1/me'", :warn)
|
459
|
+
end
|
460
|
+
|
461
|
+
client = Itrp::Client.new(api_token: 'secret', max_retry_time: 16)
|
462
|
+
allow(client).to receive(:sleep)
|
463
|
+
response = client.get('me')
|
464
|
+
expect(stub).to have_been_requested.times(4)
|
465
|
+
expect(response.valid?).to be_falsey
|
466
|
+
expect(response.message).to eq("500: No Response from Server - network error for 'api.itrp.com:443/v1/me'")
|
467
|
+
end
|
468
|
+
|
469
|
+
it 'should return the response after retry succeeds' do
|
470
|
+
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_raise(StandardError.new('network error')).then.to_return(body: {name: 'my name'}.to_json)
|
471
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
472
|
+
expect_log("Request failed, retry #1 in 2 seconds: 500: No Response from Server - network error for 'api.itrp.com:443/v1/me'", :warn)
|
473
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
474
|
+
expect_log(%(Response:\n{\n "name": "my name"\n}), :debug )
|
475
|
+
|
476
|
+
client = Itrp::Client.new(api_token: 'secret', max_retry_time: 16)
|
477
|
+
allow(client).to receive(:sleep)
|
478
|
+
response = client.get('me')
|
479
|
+
expect(stub).to have_been_requested.times(2)
|
480
|
+
expect(response.valid?).to be_truthy
|
481
|
+
expect(response[:name]).to eq('my name')
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
context 'rate limiting' do
|
486
|
+
it 'should not block on rate limit when block_at_rate_limit is false' do
|
487
|
+
stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(status: 429, body: {message: 'Too Many Requests'}.to_json)
|
488
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
489
|
+
expect_log("Request failed: 429: Too Many Requests", :error)
|
490
|
+
|
491
|
+
client = Itrp::Client.new(api_token: 'secret', block_at_rate_limit: false)
|
492
|
+
response = client.get('me')
|
493
|
+
expect(stub).to have_been_requested.times(1)
|
494
|
+
expect(response.valid?).to be_falsey
|
495
|
+
expect(response.message).to eq('429: Too Many Requests')
|
496
|
+
end
|
497
|
+
|
498
|
+
it 'should block on rate limit when block_at_rate_limit is true' do
|
499
|
+
stub = stub_request(:get, 'https://api.itrp.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)
|
500
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
501
|
+
expect_log('Request throttled, trying again in 5 minutes: 429: Too Many Requests', :warn)
|
502
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
|
503
|
+
expect_log(%(Response:\n{\n "name": "my name"\n}), :debug )
|
504
|
+
|
505
|
+
client = Itrp::Client.new(api_token: 'secret', block_at_rate_limit: true)
|
506
|
+
allow(client).to receive(:sleep)
|
507
|
+
response = client.get('me')
|
508
|
+
expect(stub).to have_been_requested.times(2)
|
509
|
+
expect(response.valid?).to be_truthy
|
510
|
+
expect(response[:name]).to eq('my name')
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
context 'logger' do
|
515
|
+
before(:each) do
|
516
|
+
@logger = Logger.new(STDOUT)
|
517
|
+
@client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1, logger: @logger)
|
518
|
+
end
|
519
|
+
|
520
|
+
it 'should be possible to override the default logger' do
|
521
|
+
stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(body: {name: 'my name'}.to_json)
|
522
|
+
expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug, @logger )
|
523
|
+
expect_log(%(Response:\n{\n "name": "my name"\n}), :debug, @logger )
|
524
|
+
@client.get('me')
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|