itrp-client 1.1.3 → 1.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: de6fdad59c1fdf0bc47384448260e98b2434f323
4
- data.tar.gz: 976f89ddc9f004e7c716c0e3bbe7eea7fc8df4b9
3
+ metadata.gz: 3191ba404199a9c372100cfbaf4d098b4bf421ea
4
+ data.tar.gz: 6af16dbe18fe503f3186cb8ed1110bf8e00b518c
5
5
  SHA512:
6
- metadata.gz: d4a0ca0784ddb94126cfc2bd76b8ca86c85933d3eecb7603ab177141827345771541d44014c9a78df293a0f3903e83ab2a76ca4ba576b43c24431dc65c38a88f
7
- data.tar.gz: 806497d4dda808649e0d99d239c8eed4896367668465f5eec71b7a87d3a5aa51201cc4061379395b87e326351d5e53739a76be19d18c6abe468457af6acbe2b3
6
+ metadata.gz: 722a14bab6be83a23c483d2dece405bf680901dbdff3f7389211e7dcea9f6af2028cdc0f07510e091f045ce8909fa7fbb432e486436307e10481ad96150a583e
7
+ data.tar.gz: fb8f9090c5ce0c3da2c81e4d8431521a132f448c94a493ebed8ff459f527eecf0b5173341ad5e36a949f2f5fe3438d170d5f5790c0c7d048905cbbbb64bd029a
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- itrp-client (1.0.13)
4
+ itrp-client (1.1.1)
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.5.1)
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.4.0)
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.2.3)
25
+ hashdiff (0.3.1)
25
26
  i18n (0.7.0)
26
27
  json (1.8.3)
27
- mime-types (3.0)
28
+ mime-types (3.1)
28
29
  mime-types-data (~> 3.2015)
29
- mime-types-data (3.2015.1120)
30
- minitest (5.8.4)
31
- rake (10.5.0)
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.11.1)
48
+ simplecov (0.12.0)
47
49
  docile (~> 1.1.0)
48
- json (~> 1.8)
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.22.6)
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.10.6
73
+ 1.11.2
data/README.md CHANGED
@@ -308,6 +308,13 @@ By default all actions on the ITRP Client will block until the ITRP API is acces
308
308
 
309
309
  By setting the _block_at_rate_limit_ to `true` in the [configuration](#global-configuration) all actions will also block in case the [rate limit](http://developer.itrp.com/v1/#rate-limiting) is reached. The action is retried every 5 minutes until the [rate limit](http://developer.itrp.com/v1/#rate-limiting) is lifted again, which might take up to 1 hour.
310
310
 
311
+ ### Translations
312
+
313
+ When exporting translations, the _locale_ parameter is required:
314
+
315
+ ```
316
+ response = Itrp::Client.new.export('translations', nil, false, 'nl')
317
+ ```
311
318
 
312
319
  ### Exception handling
313
320
 
@@ -151,10 +151,12 @@ module Itrp
151
151
  # @param types: The types to export, e.g. person, organization, people_contact_details
152
152
  # @param from: Retrieve all files since a given data and time
153
153
  # @param block_until_completed: Set to true to monitor the export progress
154
+ # @param locale: Required for translations export
154
155
  # @raise Itrp::Exception in case the export progress could not be monitored
155
- def export(types, from = nil, block_until_completed = false)
156
+ def export(types, from = nil, block_until_completed = false, locale = nil)
156
157
  data = {type: [types].flatten.join(',')}
157
158
  data[:from] = from unless from.blank?
159
+ data[:locale] = locale unless locale.blank?
158
160
  response = post('/export', data)
159
161
  if response.valid?
160
162
  if response.raw.code.to_s == '204'
@@ -1,5 +1,5 @@
1
1
  module Itrp
2
2
  class Client
3
- VERSION = '1.1.3'
3
+ VERSION = '1.1.4'
4
4
  end
5
5
  end
@@ -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,550 @@
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: '', status: 204)
222
+ response = client.delete('people', {id: 'value'}, {'X-ITRP-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 = Itrp::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.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(body: xml)
237
+ expect_log('Sending GET request to api.itrp.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.itrp.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.itrp.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(Itrp::Attachments).not_to receive(:new)
255
+ stub_request(:get, 'https://api.itrp.com/v1/requests/777?attachments=/tmp/first.png,/tmp/second.zip&note=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('Itrp::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(Itrp::Attachments).to receive(:new).with(@client){ attachments }
272
+ 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)
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 = Itrp::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/comma-separated-values\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
+ end
294
+
295
+ it 'should import a CSV file' do
296
+ 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)
297
+ expect_log("Import file '#{@fixture_dir}/people.csv' successfully uploaded with token '68ef5ef0f64c0'.")
298
+
299
+ response = @client.import(File.new("#{@fixture_dir}/people.csv"), 'people')
300
+ expect(response[:token]).to eq('68ef5ef0f64c0')
301
+ end
302
+
303
+ it 'should import a CSV file by filename' do
304
+ 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)
305
+ response = @client.import("#{@fixture_dir}/people.csv", 'people')
306
+ expect(response[:token]).to eq('68ef5ef0f64c0')
307
+ end
308
+
309
+ it 'should wait for the import to complete' do
310
+ 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)
311
+ progress_stub = stub_request(:get, 'https://api.itrp.com/v1/import/68ef5ef0f64c0').with(basic_auth: ['secret', 'x'])
312
+ .to_return(@import_queued_response, @import_processing_response)
313
+ .then.to_raise(StandardError.new('network error'))
314
+ .then.to_return(@import_done_response)
315
+
316
+ # verify the correct log statement are made
317
+ expect_log('Sending POST request to api.itrp.com:443/v1/import', :debug)
318
+ expect_log("Response:\n{\n \"token\": \"68ef5ef0f64c0\"\n}", :debug)
319
+ expect_log("Import file '#{@fixture_dir}/people.csv' successfully uploaded with token '68ef5ef0f64c0'.")
320
+ expect_log('Sending GET request to api.itrp.com:443/v1/import/68ef5ef0f64c0', :debug)
321
+ expect_log("Response:\n{\n \"state\": \"queued\"\n}", :debug)
322
+ expect_log("Import of '#{@fixture_dir}/people.csv' is queued. Checking again in 30 seconds.", :debug)
323
+ expect_log('Sending GET request to api.itrp.com:443/v1/import/68ef5ef0f64c0', :debug)
324
+ expect_log("Response:\n{\n \"state\": \"processing\"\n}", :debug)
325
+ expect_log("Import of '#{@fixture_dir}/people.csv' is processing. Checking again in 30 seconds.", :debug)
326
+ expect_log('Sending GET request to api.itrp.com:443/v1/import/68ef5ef0f64c0', :debug)
327
+ expect_log("Request failed: 500: No Response from Server - network error for 'api.itrp.com:443/v1/import/68ef5ef0f64c0'", :error)
328
+ expect_log('Sending GET request to api.itrp.com:443/v1/import/68ef5ef0f64c0', :debug)
329
+ 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)
330
+
331
+ response = @client.import("#{@fixture_dir}/people.csv", 'people', true)
332
+ expect(response[:state]).to eq('done')
333
+ expect(response[:results][:updated]).to eq(1)
334
+ expect(progress_stub).to have_been_requested.times(4)
335
+ end
336
+
337
+ it 'should wait for the import to fail' do
338
+ 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)
339
+ 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)
340
+
341
+ 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")
342
+ expect(progress_stub).to have_been_requested.times(4)
343
+ end
344
+
345
+ it 'should not continue when there is an error connecting to ITRP' do
346
+ 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)
347
+ progress_stub = stub_request(:get, 'https://api.itrp.com/v1/import/68ef5ef0f64c0').with(basic_auth: ['secret', 'x'])
348
+ .to_return(@import_queued_response, @import_processing_response)
349
+ .then.to_raise(StandardError.new('network error')) # twice
350
+
351
+ 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'")
352
+ expect(progress_stub).to have_been_requested.times(4)
353
+ end
354
+
355
+ it 'should return an invalid response in case waiting for progress is false' do
356
+ 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)
357
+ response = @client.import("#{@fixture_dir}/people.csv", 'people', false)
358
+ expect(response.valid?).to be_falsey
359
+ expect(response.message).to eq('oops!')
360
+ end
361
+
362
+ it 'should raise an UploadFailed exception in case waiting for progress is true' do
363
+ 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)
364
+ expect{ @client.import("#{@fixture_dir}/people.csv", 'people', true) }.to raise_error(Itrp::UploadFailed, 'Failed to queue people import. oops!')
365
+ end
366
+
367
+ end
368
+
369
+
370
+ context 'export' do
371
+ before(:each) do
372
+ @client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
373
+
374
+ @export_queued_response = {body: {state: 'queued'}.to_json}
375
+ @export_processing_response = {body: {state: 'processing'}.to_json}
376
+ @export_done_response = {body: {state: 'done', url: 'https://download.example.com/export.zip?AWSAccessKeyId=12345'}.to_json}
377
+ allow(@client).to receive(:sleep)
378
+ end
379
+
380
+ it 'should export multiple types' do
381
+ 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)
382
+ expect_log("Export for 'people,people_contact_details' successfully queued with token '68ef5ef0f64c0'.")
383
+
384
+ response = @client.export(['people', 'people_contact_details'])
385
+ expect(response[:token]).to eq('68ef5ef0f64c0')
386
+ end
387
+
388
+ it 'should indicate when nothing is exported' 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(status: 204)
390
+ expect_log("No changed records for 'people' since 2012-03-30T23:00:00+00:00.")
391
+
392
+ response = @client.export('people', DateTime.new(2012,03,30,23,00,00))
393
+ expect(response[:token]).to be_nil
394
+ end
395
+
396
+ it 'should export since a certain time' do
397
+ 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)
398
+ expect_log("Export for 'people' successfully queued with token '68ef5ef0f64c0'.")
399
+
400
+ response = @client.export('people', DateTime.new(2012,03,30,23,00,00))
401
+ expect(response[:token]).to eq('68ef5ef0f64c0')
402
+ end
403
+
404
+ it 'should export with locale' do
405
+ stub_request(:post, 'https://api.itrp.com/v1/export').with(basic_auth: ['secret', 'x']).with(body: {type: 'translations', locale: 'nl'}).to_return(body: {token: '68ef5ef0f64c0'}.to_json)
406
+ expect_log("Export for 'translations' successfully queued with token '68ef5ef0f64c0'.")
407
+
408
+ response = @client.export('translations', nil, nil, 'nl')
409
+ expect(response[:token]).to eq('68ef5ef0f64c0')
410
+ end
411
+
412
+ it 'should wait for the export to complete' do
413
+ 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)
414
+ progress_stub = stub_request(:get, 'https://api.itrp.com/v1/export/68ef5ef0f64c0').with(basic_auth: ['secret', 'x'])
415
+ .to_return(@export_queued_response, @export_processing_response)
416
+ .then.to_raise(StandardError.new('network error'))
417
+ .then.to_return(@export_done_response)
418
+
419
+ # verify the correct log statement are made
420
+ expect_log('Sending POST request to api.itrp.com:443/v1/export', :debug)
421
+ expect_log(%(Response:\n{\n "token": "68ef5ef0f64c0"\n}), :debug)
422
+ expect_log("Export for 'people' successfully queued with token '68ef5ef0f64c0'.")
423
+ expect_log('Sending GET request to api.itrp.com:443/v1/export/68ef5ef0f64c0', :debug)
424
+ expect_log(%(Response:\n{\n "state": "queued"\n}), :debug)
425
+ expect_log("Export of 'people' is queued. Checking again in 30 seconds.", :debug)
426
+ expect_log('Sending GET request to api.itrp.com:443/v1/export/68ef5ef0f64c0', :debug)
427
+ expect_log(%(Response:\n{\n "state": "processing"\n}), :debug)
428
+ expect_log("Export of 'people' is processing. Checking again in 30 seconds.", :debug)
429
+ expect_log('Sending GET request to api.itrp.com:443/v1/export/68ef5ef0f64c0', :debug)
430
+ expect_log("Request failed: 500: No Response from Server - network error for 'api.itrp.com:443/v1/export/68ef5ef0f64c0'", :error)
431
+ expect_log('Sending GET request to api.itrp.com:443/v1/export/68ef5ef0f64c0', :debug)
432
+ expect_log(%(Response:\n{\n "state": "done",\n "url": "https://download.example.com/export.zip?AWSAccessKeyId=12345"\n}), :debug)
433
+
434
+ response = @client.export('people', nil, true)
435
+ expect(response[:state]).to eq('done')
436
+ expect(response[:url]).to eq('https://download.example.com/export.zip?AWSAccessKeyId=12345')
437
+ expect(progress_stub).to have_been_requested.times(4)
438
+ end
439
+
440
+ it 'should not continue when there is an error connecting to ITRP' do
441
+ 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)
442
+ progress_stub = stub_request(:get, 'https://api.itrp.com/v1/export/68ef5ef0f64c0').with(basic_auth: ['secret', 'x'])
443
+ .to_return(@export_queued_response, @export_processing_response)
444
+ .then.to_raise(StandardError.new('network error')) # twice
445
+
446
+ 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'")
447
+ expect(progress_stub).to have_been_requested.times(4)
448
+ end
449
+
450
+ it 'should return an invalid response in case waiting for progress is false' do
451
+ 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)
452
+ response = @client.export('people')
453
+ expect(response.valid?).to be_falsey
454
+ expect(response.message).to eq('oops!')
455
+ end
456
+
457
+ it 'should raise an UploadFailed exception in case waiting for progress is true' do
458
+ 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)
459
+ expect{ @client.export('people', nil, true) }.to raise_error(Itrp::UploadFailed, "Failed to queue 'people' export. oops!")
460
+ end
461
+
462
+ end
463
+
464
+ context 'retry' do
465
+ it 'should not retry when max_retry_time = -1' do
466
+ stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_raise(StandardError.new('network error'))
467
+ expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
468
+ expect_log("Request failed: 500: No Response from Server - network error for 'api.itrp.com:443/v1/me'", :error)
469
+
470
+ client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1)
471
+ response = client.get('me')
472
+ expect(stub).to have_been_requested.times(1)
473
+ expect(response.valid?).to be_falsey
474
+ expect(response.message).to eq("500: No Response from Server - network error for 'api.itrp.com:443/v1/me'")
475
+ end
476
+
477
+ it 'should not retry 4 times when max_retry_time = 16' do
478
+ stub = stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_raise(StandardError.new('network error'))
479
+ [2,4,8,16].each_with_index do |secs, i|
480
+ expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
481
+ 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)
482
+ end
483
+
484
+ client = Itrp::Client.new(api_token: 'secret', max_retry_time: 16)
485
+ allow(client).to receive(:sleep)
486
+ response = client.get('me')
487
+ expect(stub).to have_been_requested.times(4)
488
+ expect(response.valid?).to be_falsey
489
+ expect(response.message).to eq("500: No Response from Server - network error for 'api.itrp.com:443/v1/me'")
490
+ end
491
+
492
+ it 'should return the response after retry succeeds' do
493
+ 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)
494
+ expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
495
+ expect_log("Request failed, retry #1 in 2 seconds: 500: No Response from Server - network error for 'api.itrp.com:443/v1/me'", :warn)
496
+ expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
497
+ expect_log(%(Response:\n{\n "name": "my name"\n}), :debug )
498
+
499
+ client = Itrp::Client.new(api_token: 'secret', max_retry_time: 16)
500
+ allow(client).to receive(:sleep)
501
+ response = client.get('me')
502
+ expect(stub).to have_been_requested.times(2)
503
+ expect(response.valid?).to be_truthy
504
+ expect(response[:name]).to eq('my name')
505
+ end
506
+ end
507
+
508
+ context 'rate limiting' do
509
+ it 'should not block on rate limit when block_at_rate_limit is false' do
510
+ 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)
511
+ expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
512
+ expect_log("Request failed: 429: Too Many Requests", :error)
513
+
514
+ client = Itrp::Client.new(api_token: 'secret', block_at_rate_limit: false)
515
+ response = client.get('me')
516
+ expect(stub).to have_been_requested.times(1)
517
+ expect(response.valid?).to be_falsey
518
+ expect(response.message).to eq('429: Too Many Requests')
519
+ end
520
+
521
+ it 'should block on rate limit when block_at_rate_limit is true' do
522
+ 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)
523
+ expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
524
+ expect_log('Request throttled, trying again in 5 minutes: 429: Too Many Requests', :warn)
525
+ expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug )
526
+ expect_log(%(Response:\n{\n "name": "my name"\n}), :debug )
527
+
528
+ client = Itrp::Client.new(api_token: 'secret', block_at_rate_limit: true)
529
+ allow(client).to receive(:sleep)
530
+ response = client.get('me')
531
+ expect(stub).to have_been_requested.times(2)
532
+ expect(response.valid?).to be_truthy
533
+ expect(response[:name]).to eq('my name')
534
+ end
535
+ end
536
+
537
+ context 'logger' do
538
+ before(:each) do
539
+ @logger = Logger.new(STDOUT)
540
+ @client = Itrp::Client.new(api_token: 'secret', max_retry_time: -1, logger: @logger)
541
+ end
542
+
543
+ it 'should be possible to override the default logger' do
544
+ stub_request(:get, 'https://api.itrp.com/v1/me').with(basic_auth: ['secret', 'x']).to_return(body: {name: 'my name'}.to_json)
545
+ expect_log('Sending GET request to api.itrp.com:443/v1/me', :debug, @logger )
546
+ expect_log(%(Response:\n{\n "name": "my name"\n}), :debug, @logger )
547
+ @client.get('me')
548
+ end
549
+ end
550
+ end