4me-sdk 1.2.0 → 2.0.0.pre.rc.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,7 @@ require 'mime/types'
8
8
  # Created:: 22 Feb 2008
9
9
  module Sdk4me
10
10
  module Multipart
11
- VERSION = "1.0.0" unless const_defined?(:VERSION)
11
+ VERSION = '1.0.0'.freeze unless const_defined?(:VERSION)
12
12
 
13
13
  # Formats a given hash as a multipart form post
14
14
  # If a hash value responds to :string or :read messages, then it is
@@ -16,10 +16,10 @@ module Sdk4me
16
16
  # to be a string
17
17
  class Post
18
18
  # We have to pretend like we're a web browser...
19
- USERAGENT = "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" unless const_defined?(:USERAGENT)
20
- BOUNDARY = '0123456789ABLEWASIEREISAWELBA9876543210' unless const_defined?(:BOUNDARY)
21
- CONTENT_TYPE = "multipart/form-data; boundary=#{ BOUNDARY }" unless const_defined?(:CONTENT_TYPE)
22
- HEADER = { 'Content-Type' => CONTENT_TYPE, 'User-Agent' => USERAGENT } unless const_defined?(:HEADER)
19
+ USERAGENT = "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 4me/#{Sdk4me::Client::VERSION}".freeze unless const_defined?(:USERAGENT)
20
+ BOUNDARY = '0123456789ABLEWASIEREISAWELBA9876543210'.freeze unless const_defined?(:BOUNDARY)
21
+ CONTENT_TYPE = "multipart/form-data; boundary=#{BOUNDARY}".freeze unless const_defined?(:CONTENT_TYPE)
22
+ HEADER = { 'Content-Type' => CONTENT_TYPE, 'User-Agent' => USERAGENT }.freeze unless const_defined?(:HEADER)
23
23
 
24
24
  def self.prepare_query(params)
25
25
  fp = []
@@ -33,24 +33,22 @@ module Sdk4me
33
33
  end
34
34
 
35
35
  # Assemble the request body using the special multipart format
36
- query = fp.map{ |p| "--#{BOUNDARY}\r\n#{p.to_multipart}" }.join + "--#{BOUNDARY}--"
37
- return query, HEADER
36
+ query = fp.map { |p| "--#{BOUNDARY}\r\n#{p.to_multipart}" }.join + "--#{BOUNDARY}--"
37
+ [query, HEADER]
38
38
  end
39
39
  end
40
40
 
41
- private
42
-
43
41
  # Formats a basic string key/value pair for inclusion with a multipart post
44
42
  class StringParam
45
43
  attr_accessor :k, :v
46
44
 
47
- def initialize(k, v)
48
- @k = k
49
- @v = v
45
+ def initialize(key, value)
46
+ @k = key
47
+ @v = value
50
48
  end
51
49
 
52
50
  def to_multipart
53
- return %(Content-Disposition: form-data; name="#{CGI::escape(k.to_s)}"\r\n\r\n#{v}\r\n)
51
+ %(Content-Disposition: form-data; name="#{CGI.escape(k.to_s)}"\r\n\r\n#{v}\r\n)
54
52
  end
55
53
  end
56
54
 
@@ -59,17 +57,17 @@ module Sdk4me
59
57
  class FileParam
60
58
  attr_accessor :k, :filename, :content
61
59
 
62
- def initialize(k, filename, content)
63
- @k = k
60
+ def initialize(key, filename, content)
61
+ @k = key
64
62
  @filename = filename
65
63
  @content = content
66
64
  end
67
65
 
68
66
  def to_multipart
69
67
  # If we can tell the possible mime-type from the filename, use the first in the list; otherwise, use "application/octet-stream"
70
- mime_type = MIME::Types.type_for(filename)[0] || MIME::Types["application/octet-stream"][0]
71
- return %(Content-Disposition: form-data; name="#{CGI::escape(k.to_s)}"; filename="#{filename}"\r\nContent-Type: #{ mime_type.simplified }\r\n\r\n#{ content }\r\n)
68
+ mime_type = MIME::Types.type_for(filename)[0] || MIME::Types['application/octet-stream'][0]
69
+ %(Content-Disposition: form-data; name="#{CGI.escape(k.to_s)}"; filename="#{filename}"\r\nContent-Type: #{mime_type.simplified}\r\n\r\n#{content}\r\n)
72
70
  end
73
71
  end
74
72
  end
75
- end
73
+ end
@@ -1,19 +1,13 @@
1
1
  module Sdk4me
2
2
  class Response
3
+ attr_reader :request, :response
4
+ alias raw response
5
+
3
6
  def initialize(request, response)
4
7
  @request = request
5
8
  @response = response
6
9
  end
7
10
 
8
- def request
9
- @request
10
- end
11
-
12
- def response
13
- @response
14
- end
15
- alias_method :raw, :response
16
-
17
11
  def body
18
12
  @response.body
19
13
  end
@@ -22,16 +16,17 @@ module Sdk4me
22
16
  # If the response is not +valid?+ it is a Hash with 'message' and optionally 'errors'
23
17
  def json
24
18
  return @json if defined?(@json)
19
+
25
20
  # no content, no JSON
26
21
  if @response.code.to_s == '204'
27
22
  data = {}
28
23
  elsif @response.body.blank?
29
24
  # no body, no json
30
- data = {message: @response.message.blank? ? 'empty body' : @response.message.strip}
25
+ data = { message: @response.message.blank? ? 'empty body' : @response.message.strip }
31
26
  end
32
27
  begin
33
28
  data ||= JSON.parse(@response.body)
34
- rescue ::Exception => e
29
+ rescue StandardError => e
35
30
  data = { message: "Invalid JSON - #{e.message} for:\n#{@response.body}" }
36
31
  end
37
32
  # indifferent access to hashes
@@ -57,7 +52,7 @@ module Sdk4me
57
52
  def valid?
58
53
  message.nil?
59
54
  end
60
- alias_method :success?, :valid?
55
+ alias success? valid?
61
56
 
62
57
  # +true+ in case of a HTTP 5xx error
63
58
  def failure?
@@ -69,15 +64,19 @@ module Sdk4me
69
64
  # @param keys: a single key or a key-path separated by comma
70
65
  def[](*keys)
71
66
  values = json.is_a?(Array) ? json : [json]
72
- keys.each { |key| values = values.map{ |value| value.is_a?(Hash) ? value[key] : nil} }
67
+ keys.each { |key| values = values.map { |value| value.is_a?(Hash) ? value[key] : nil } }
73
68
  json.is_a?(Array) ? values : values.first
74
69
  end
75
70
 
76
71
  # The nr of resources found
77
72
  def size
78
- @size ||= message ? 0 : json.is_a?(Array) ? json.size : 1
73
+ @size ||= if message
74
+ 0
75
+ else
76
+ json.is_a?(Array) ? json.size : 1
77
+ end
79
78
  end
80
- alias :count :size
79
+ alias count size
81
80
 
82
81
  # pagination - per page
83
82
  def per_page
@@ -103,12 +102,12 @@ module Sdk4me
103
102
  # Link: <https://api.4me.com/v1/requests?page=1&per_page=25>; rel="first", <https://api.4me.com/v1/requests?page=2&per_page=25>; rel="prev", etc.
104
103
  def pagination_link(relation)
105
104
  # split on ',' select the [url] in '<[url]>; rel="[relation]"', compact to all url's found (at most one) and take the first
106
- (@pagination_links ||= {})[relation] ||= @response.header['Link'] && @response.header['Link'].split(/,\s*<?/).map{ |link| link[/^\s*<?(.*?)>?;\s*rel="#{relation.to_s}"\s*$/, 1] }.compact.first
105
+ (@pagination_links ||= {})[relation] ||= @response.header['Link'] && @response.header['Link'].split(/,\s*<?/).map { |link| link[/^\s*<?(.*?)>?;\s*rel="#{relation}"\s*$/, 1] }.compact.first
107
106
  end
108
107
 
109
108
  # pagination urls (relative paths without server) - relations :first, :prev, :next, :last
110
109
  def pagination_relative_link(relation)
111
- (@pagination_relative_links ||= {})[relation] ||= pagination_link(relation) && pagination_link(relation)[/^https?:\/\/[^\/]*(.*)/, 1]
110
+ (@pagination_relative_links ||= {})[relation] ||= pagination_link(relation) && pagination_link(relation)[%r{^https?://[^/]*(.*)}, 1]
112
111
  end
113
112
 
114
113
  # +true+ if the response is invalid because of throttling
@@ -117,12 +116,11 @@ module Sdk4me
117
116
  end
118
117
 
119
118
  def retry_after
120
- @current_page ||= @response.header['Retry-After'].to_i
119
+ @retry_after ||= @response.header['Retry-After'].to_i
121
120
  end
122
121
 
123
122
  def to_s
124
123
  valid? ? json.to_s : message
125
124
  end
126
-
127
125
  end
128
126
  end
@@ -1,5 +1,5 @@
1
1
  module Sdk4me
2
2
  class Client
3
- VERSION = '1.2.0'
3
+ VERSION = '2.0.0-rc.1'.freeze
4
4
  end
5
5
  end
@@ -1,262 +1,341 @@
1
1
  require 'spec_helper'
2
+ require 'tempfile'
2
3
 
3
4
  describe Sdk4me::Attachments do
4
-
5
- def attachments(authentication)
6
- (@attachments ||= {})[authentication] ||= begin
7
- client = if authentication == :api_token
8
- Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
9
- else
10
- Sdk4me::Client.new(access_token: 'secret', max_retry_time: -1)
11
- end
12
- Sdk4me::Attachments.new(client)
13
- end
5
+ def attachments(authentication, path)
6
+ @client = if authentication == :api_token
7
+ Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
8
+ else
9
+ Sdk4me::Client.new(access_token: 'secret', max_retry_time: -1)
10
+ end
11
+ Sdk4me::Attachments.new(@client, path)
14
12
  end
15
13
 
16
14
  def credentials(authentication)
17
15
  if authentication == :api_token
18
- { basic_auth: ['secret', 'x'] }
16
+ { basic_auth: %w[secret x] }
19
17
  else
20
- { headers: {'Authorization' => 'Bearer secret'} }
18
+ { headers: { 'Authorization' => 'Bearer secret' } }
21
19
  end
22
20
  end
23
21
 
24
- [:api_token, :access_token].each do |authentication|
25
-
26
- context "#{authentication} - " do
27
- context 'upload_attachments!' do
28
- context 'normal' do
29
- it 'should not do anything when no :attachments are present' do
30
- expect(attachments(authentication).upload_attachments!('/requests', {status: :in_progress})).to be_nil
22
+ %i[api_token access_token].each do |authentication|
23
+ context "#{authentication} -" do
24
+ context 'upload_attachments! -' do
25
+ context 'field attachments' do
26
+ it 'should not do anything when no attachments are present' do
27
+ a = attachments(authentication, '/requests')
28
+ expect(@client).not_to receive(:send_file)
29
+ a.upload_attachments!({ status: :in_progress })
31
30
  end
32
31
 
33
- it 'should not do anything when :attachments is nil' do
34
- expect(attachments(authentication).upload_attachments!('/requests', {attachments: nil})).to be_nil
32
+ it 'should not do anything when attachments is nil' do
33
+ a = attachments(authentication, '/requests')
34
+ expect(@client).not_to receive(:send_file)
35
+ a.upload_attachments!({ note_attachments: nil })
35
36
  end
36
37
 
37
- it 'should not do anything when :attachments is empty' do
38
- expect(attachments(authentication).upload_attachments!('/requests', {attachments: []})).to be_nil
39
- expect(attachments(authentication).upload_attachments!('/requests', {attachments: [nil]})).to be_nil
38
+ it 'should not do anything when attachments is empty' do
39
+ a = attachments(authentication, '/requests')
40
+ expect(@client).not_to receive(:send_file)
41
+ a.upload_attachments!({ note_attachments: [] })
42
+ a.upload_attachments!({ note_attachments: [nil] })
40
43
  end
41
44
 
42
- it 'should show a error if no attachment may be uploaded' do
43
- stub_request(:get, 'https://api.4me.com/v1/sites/1?attachment_upload_token=true').with(credentials(authentication)).to_return(body: {name: 'site 1'}.to_json)
44
- expect_log('Attachments not allowed for /sites/1', :error)
45
- expect(attachments(authentication).upload_attachments!('/sites/1', {attachments: ['file1.png']})).to be_nil
45
+ it 'should raise an error if no attachment provider can be determined' do
46
+ a = attachments(authentication, '/requests')
47
+ expect(@client).not_to receive(:send_file)
48
+ stub_request(:get, 'https://api.4me.com/v1/attachments/storage').with(credentials(authentication)).to_return(status: 404, body: { message: 'Not Found' }.to_json)
49
+ expect_log('GET request to api.4me.com:443/v1/attachments/storage failed: 404: Not Found', :error)
50
+ expect_log('Attachment upload failed: No provider found', :error)
51
+ expect { a.upload_attachments!({ note_attachments: ['file1.png'] }) }.to raise_error(::Sdk4me::UploadFailed, 'Attachment upload failed: No provider found')
46
52
  end
47
53
 
48
- it 'should raise an exception if no attachment may be uploaded' do
49
- stub_request(:get, 'https://api.4me.com/v1/sites/1?attachment_upload_token=true').with(credentials(authentication)).to_return(body: {name: 'site 1'}.to_json)
50
- message = 'Attachments not allowed for /sites/1'
51
- expect{ attachments(authentication).upload_attachments!('/sites/1', {attachments: ['file1.png'], attachments_exception: true}) }.to raise_error(::Sdk4me::UploadFailed, message)
52
- end
53
-
54
- it 'should add /new to the path for new records' do
55
- stub_request(:get, 'https://api.4me.com/v1/sites/new?attachment_upload_token=true').with(credentials(authentication)).to_return(body: {missing: 'storage'}.to_json)
56
- expect_log('Attachments not allowed for /sites', :error)
57
- expect(attachments(authentication).upload_attachments!('/sites', {attachments: ['file1.png']})).to be_nil
58
- end
54
+ it 'should upload' do
55
+ a = attachments(authentication, '/requests')
56
+ resp = {
57
+ provider: 'local',
58
+ upload_uri: 'https://widget.example.com/attachments',
59
+ local: {
60
+ key: 'attachments/5/requests/000/000/777/abc/${filename}',
61
+ x_4me_expiration: '2020-11-01T23:59:59Z',
62
+ x_4me_signature: 'foobar'
63
+ }
64
+ }
65
+ stub_request(:get, 'https://api.4me.com/v1/attachments/storage').with(credentials(authentication)).to_return(status: 200, body: resp.to_json)
59
66
 
60
- [ [:requests, :note],
61
- [:problems, :note],
62
- [:contracts, :remarks],
63
- [:cis, :remarks],
64
- [:flsas, :remarks],
65
- [:slas, :remarks],
66
- [:service_instances, :remarks],
67
- [:service_offerings, :summary],
68
- [:any_other_model, :note]].each do |model, attribute|
69
-
70
- it "should replace :attachments with :#{attribute}_attachments after upload at /#{model}" do
71
- stub_request(:get, "https://api.4me.com/v1/#{model}/new?attachment_upload_token=true").with(credentials(authentication)).to_return(body: {storage_upload: 'conf'}.to_json)
72
- expect(attachments(authentication)).to receive(:upload_attachment).with('conf', 'file1.png', false).ordered{ 'uploaded file1.png' }
73
- expect(attachments(authentication)).to receive(:upload_attachment).with('conf', 'file2.zip', false).ordered{ 'uploaded file2.zip' }
74
- data = {leave: 'me alone', attachments: %w(file1.png file2.zip)}
75
- attachments(authentication).upload_attachments!("/#{model}", data)
76
- expect(data[:attachments]).to be_nil
77
- expect(data[:leave]).to eq('me alone')
78
- expect(data[:"#{attribute}_attachments"]).to eq(['uploaded file1.png', 'uploaded file2.zip'].to_json)
79
- end
80
- end
67
+ expect(a).to(receive(:upload_attachment).with('/tmp/file1.png').ordered { { key: 'attachments/5/requests/000/000/777/abc/file1.png', filesize: 1234 } })
68
+ expect(a).to(receive(:upload_attachment).with('/tmp/file2.zip').ordered { { key: 'attachments/5/requests/000/000/777/abc/file2.zip', filesize: 9876 } })
81
69
 
82
- it 'should set raise_exception flag to true when :attachments_exception is set' do
83
- stub_request(:get, 'https://api.4me.com/v1/requests/new?attachment_upload_token=true').with(credentials(authentication)).to_return(body: {storage_upload: 'conf'}.to_json)
84
- expect(attachments(authentication)).to receive(:upload_attachment).with('conf', 'file1.png', true).ordered{ 'uploaded file1.png' }
85
- data = {leave: 'me alone', attachments: 'file1.png', attachments_exception: true}
86
- attachments(authentication).upload_attachments!('/requests', data)
87
- expect(data[:attachments]).to be_nil
88
- expect(data[:attachments_exception]).to be_nil
89
- expect(data[:leave]).to eq('me alone')
90
- expect(data[:note_attachments]).to eq(['uploaded file1.png'].to_json)
70
+ data = { subject: 'Foobar', note_attachments: ['/tmp/file1.png', '/tmp/file2.zip'] }
71
+ a.upload_attachments!(data)
72
+ expect(data).to eq({ subject: 'Foobar', note_attachments: [
73
+ { filesize: 1234, key: 'attachments/5/requests/000/000/777/abc/file1.png' },
74
+ { filesize: 9876, key: 'attachments/5/requests/000/000/777/abc/file2.zip' }
75
+ ] })
91
76
  end
92
77
  end
93
78
 
94
- context 'inline' do
95
- it 'should not do anything when no [attachment:...] is present in the note' do
96
- expect(attachments(authentication).upload_attachments!('/requests', {note: '[attachmen:/type]'})).to be_nil
79
+ context 'rich text inline attachments' do
80
+ it 'should not do anything when no [note_attachments: <idx>] is present in the note' do
81
+ a = attachments(authentication, '/requests')
82
+ expect(@client).not_to receive(:send_file)
83
+ data = { note: '[note_attachments: foo]' }
84
+ a.upload_attachments!(data)
85
+ expect(data).to eq({ note: '[note_attachments: foo]' })
97
86
  end
98
87
 
99
- it 'should not do anything when attachment is empty' do
100
- expect(attachments(authentication).upload_attachments!('/requests', {note: '[attachment:]'})).to be_nil
88
+ it 'should not do anything when note attachments is empty' do
89
+ a = attachments(authentication, '/requests')
90
+ expect(@client).not_to receive(:send_file)
91
+ data = { note: '[note_attachments: 0]' }
92
+ a.upload_attachments!(data)
93
+ expect(data).to eq({ note: '[note_attachments: 0]' })
101
94
  end
102
95
 
103
- it 'should show a error if no attachment may be uploaded' do
104
- stub_request(:get, 'https://api.4me.com/v1/sites/1?attachment_upload_token=true').with(credentials(authentication)).to_return(body: {name: 'site 1'}.to_json)
105
- expect_log('Attachments not allowed for /sites/1', :error)
106
- expect(attachments(authentication).upload_attachments!('/sites/1', {note: '[attachment:file1.png]'})).to be_nil
107
- end
108
-
109
- it 'should raise an exception if no attachment may be uploaded' do
110
- stub_request(:get, 'https://api.4me.com/v1/sites/1?attachment_upload_token=true').with(credentials(authentication)).to_return(body: {name: 'site 1'}.to_json)
111
- message = 'Attachments not allowed for /sites/1'
112
- expect{ attachments(authentication).upload_attachments!('/sites/1', {note: '[attachment:file1.png]', attachments_exception: true}) }.to raise_error(::Sdk4me::UploadFailed, message)
96
+ it 'should raise an error if no attachment provider can be determined' do
97
+ a = attachments(authentication, '/requests')
98
+ expect(@client).not_to receive(:send_file)
99
+ stub_request(:get, 'https://api.4me.com/v1/attachments/storage').with(credentials(authentication)).to_return(status: 404, body: { message: 'Not Found' }.to_json)
100
+ expect_log('GET request to api.4me.com:443/v1/attachments/storage failed: 404: Not Found', :error)
101
+ expect_log('Attachment upload failed: No provider found', :error)
102
+ data = {
103
+ note: '[note_attachments: 0]', note_attachments: ['/tmp/doesnotexist.log']
104
+ }
105
+ expect { a.upload_attachments!(data) }.to raise_error(::Sdk4me::UploadFailed, 'Attachment upload failed: No provider found')
113
106
  end
114
107
 
115
- it 'should add /new to the path for new records' do
116
- stub_request(:get, 'https://api.4me.com/v1/sites/new?attachment_upload_token=true').with(credentials(authentication)).to_return(body: {missing: 'storage'}.to_json)
117
- expect_log('Attachments not allowed for /sites', :error)
118
- expect(attachments(authentication).upload_attachments!('/sites', {note: '[attachment:file1.png]'})).to be_nil
119
- end
108
+ it 'should upload' do
109
+ a = attachments(authentication, '/requests')
110
+ resp = {
111
+ provider: 'local',
112
+ upload_uri: 'https://widget.example.com/attachments',
113
+ local: {
114
+ key: 'attachments/5/requests/000/000/777/abc/${filename}',
115
+ x_4me_expiration: '2020-11-01T23:59:59Z',
116
+ x_4me_signature: 'foobar'
117
+ }
118
+ }
119
+ stub_request(:get, 'https://api.4me.com/v1/attachments/storage').with(credentials(authentication)).to_return(status: 200, body: resp.to_json)
120
120
 
121
- [ [:requests, :note],
122
- [:problems, :note],
123
- [:contracts, :remarks],
124
- [:cis, :remarks],
125
- [:flsas, :remarks],
126
- [:slas, :remarks],
127
- [:service_instances, :remarks],
128
- [:service_offerings, :summary],
129
- [:any_other_model, :note]].each do |model, attribute|
130
-
131
- it "should replace :attachments with :#{attribute}_attachments after upload at /#{model}" do
132
- stub_request(:get, "https://api.4me.com/v1/#{model}/new?attachment_upload_token=true").with(credentials(authentication)).to_return(body: {storage_upload: 'conf'}.to_json)
133
- expect(attachments(authentication)).to receive(:upload_attachment).with('conf', 'file1.png', false).ordered{ {key: 'uploaded file1.png'} }
134
- expect(attachments(authentication)).to receive(:upload_attachment).with('conf', 'file2.zip', false).ordered{ {key: 'uploaded file2.zip'} }
135
- data = {leave: 'me alone', attribute => '[attachment:file1.png] and [attachment:file2.zip]'}
136
- attachments(authentication).upload_attachments!("/#{model}", data)
137
- expect(data[:attachments]).to be_nil
138
- expect(data[:leave]).to eq('me alone')
139
- expect(data[:"#{attribute}_attachments"]).to eq([{key: 'uploaded file1.png', inline: true}, {key: 'uploaded file2.zip', inline: true}].to_json)
140
- expect(data[:"#{attribute}"]).to eq('![](uploaded file1.png) and ![](uploaded file2.zip)')
141
- end
142
- end
121
+ expect(a).to(receive(:upload_attachment).with('/tmp/file1.png').ordered { { key: 'attachments/5/requests/000/000/777/abc/file1.png', filesize: 1234 } })
122
+ expect(a).to(receive(:upload_attachment).with('/tmp/file2.jpg').ordered { { key: 'attachments/5/requests/000/000/777/abc/file2.jpg', filesize: 9876 } })
143
123
 
144
- it 'should set raise_exception flag to true when :attachments_exception is set' do
145
- stub_request(:get, 'https://api.4me.com/v1/requests/new?attachment_upload_token=true').with(credentials(authentication)).to_return(body: {storage_upload: 'conf'}.to_json)
146
- expect(attachments(authentication)).to receive(:upload_attachment).with('conf', 'file1.png', true).ordered{ {key: 'uploaded file1.png'} }
147
- data = {leave: 'me alone', note: '[attachment:file1.png]', attachments_exception: true}
148
- attachments(authentication).upload_attachments!('/requests', data)
149
- expect(data[:attachments]).to be_nil
150
- expect(data[:attachments_exception]).to be_nil
151
- expect(data[:leave]).to eq('me alone')
152
- expect(data[:note_attachments]).to eq([{key: 'uploaded file1.png', inline: true}].to_json)
153
- expect(data[:note]).to eq('![](uploaded file1.png)')
124
+ data = {
125
+ subject: 'Foobar',
126
+ note: 'Foo [note_attachments: 0] Bar [note_attachments: 1]',
127
+ note_attachments: ['/tmp/file1.png', '/tmp/file2.jpg']
128
+ }
129
+ a.upload_attachments!(data)
130
+
131
+ expect(data).to eq({
132
+ note: 'Foo ![](attachments/5/requests/000/000/777/abc/file1.png) Bar ![](attachments/5/requests/000/000/777/abc/file2.jpg)',
133
+ note_attachments: [
134
+ { filesize: 1234, inline: true, key: 'attachments/5/requests/000/000/777/abc/file1.png' },
135
+ { filesize: 9876, inline: true, key: 'attachments/5/requests/000/000/777/abc/file2.jpg' }
136
+ ],
137
+ subject: 'Foobar'
138
+ })
154
139
  end
155
140
  end
156
141
 
157
- end
158
-
159
- context 'upload_attachment' do
142
+ context 'field attachments and rich text inline attachments' do
143
+ it 'should upload, and replace the data in place' do
144
+ a = attachments(authentication, '/requests')
145
+ resp = {
146
+ provider: 'local',
147
+ upload_uri: 'https://widget.example.com/attachments',
148
+ local: {
149
+ key: 'attachments/5/requests/000/000/777/abc/${filename}',
150
+ x_4me_expiration: '2020-11-01T23:59:59Z',
151
+ x_4me_signature: 'foobar'
152
+ }
153
+ }
154
+ stub_request(:get, 'https://api.4me.com/v1/attachments/storage').with(credentials(authentication)).to_return(status: 200, body: resp.to_json)
160
155
 
161
- it 'should log an exception when the file could not be found' do
162
- expect_log('Attachment upload failed: file does not exist: unknown_file', :error)
163
- expect(attachments(authentication).send(:upload_attachment, nil, 'unknown_file', false)).to be_nil
164
- end
156
+ expect(a).to(receive(:upload_attachment).with('/tmp/file3.log').ordered { { key: 'attachments/5/requests/000/000/777/abc/file3.log', filesize: 5678 } })
157
+ expect(a).to(receive(:upload_attachment).with('/tmp/file1.png').ordered { { key: 'attachments/5/requests/000/000/777/abc/file1.png', filesize: 1234 } })
158
+ expect(a).to(receive(:upload_attachment).with('/tmp/file2.jpg').ordered { { key: 'attachments/5/requests/000/000/777/abc/file2.jpg', filesize: 9876 } })
165
159
 
166
- it 'should raise an exception when the file could not be found' do
167
- message = 'Attachment upload failed: file does not exist: unknown_file'
168
- expect{ attachments(authentication).send(:upload_attachment, nil, 'unknown_file', true) }.to raise_error(::Sdk4me::UploadFailed, message)
169
- end
170
-
171
- context 'aws' do
172
- before(:each) do
173
- @aws_conf = {
174
- provider: 'aws',
175
- upload_uri: 'https://itrp.s3.amazonaws.com/',
176
- access_key: 'AKIA6RYQ',
177
- success_url: 'https://mycompany.4me.com/s3_success?sig=99e82e8a046',
178
- policy: 'eydlgIH0=',
179
- signature: 'nbhdec4k=',
180
- upload_path: 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/'
160
+ data = {
161
+ subject: 'Foobar',
162
+ note: 'Foo [note_attachments: 2] Bar [note_attachments: 1]',
163
+ note_attachments: ['/tmp/file3.log', '/tmp/file1.png', '/tmp/file2.jpg']
181
164
  }
182
- @key_template = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}'
183
- @key = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/upload.txt'
184
-
185
- @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--"
186
- @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'}
165
+ a.upload_attachments!(data)
166
+
167
+ expect(data).to eq({
168
+ note: 'Foo ![](attachments/5/requests/000/000/777/abc/file2.jpg) Bar ![](attachments/5/requests/000/000/777/abc/file1.png)',
169
+ note_attachments: [
170
+ { filesize: 5678, key: 'attachments/5/requests/000/000/777/abc/file3.log' },
171
+ { filesize: 1234, inline: true, key: 'attachments/5/requests/000/000/777/abc/file1.png' },
172
+ { filesize: 9876, inline: true, key: 'attachments/5/requests/000/000/777/abc/file2.jpg' }
173
+ ],
174
+ subject: 'Foobar'
175
+ })
187
176
  end
188
177
 
189
- it 'should open a file from disk' do
190
- expect(attachments(authentication)).to receive(:aws_upload).with(@aws_conf, @key_template, @key, kind_of(File))
191
- expect(attachments(authentication).send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
192
- end
178
+ it 'failed uploads' do
179
+ a = attachments(authentication, '/requests')
180
+ resp = {
181
+ provider: 'local',
182
+ upload_uri: 'https://widget.example.com/attachments',
183
+ local: {
184
+ key: 'attachments/5/requests/000/000/777/abc/${filename}',
185
+ x_4me_expiration: '2020-11-01T23:59:59Z',
186
+ x_4me_signature: 'foobar'
187
+ }
188
+ }
189
+ stub_request(:get, 'https://api.4me.com/v1/attachments/storage').with(credentials(authentication)).to_return(status: 200, body: resp.to_json)
193
190
 
194
- it 'should sent the upload to AWS' do
195
- stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: 'OK', status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
196
- stub_request(:get, "https://api.4me.com/v1/s3_success?sig=99e82e8a046&key=#{@key}").with(credentials(authentication)).to_return(body: {}.to_json)
197
- expect(attachments(authentication).send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
198
- end
191
+ expect_log('Attachment upload failed: file does not exist: /tmp/doesnotexist.png', :error)
199
192
 
200
- it 'should report an error when AWS upload fails' do
201
- stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: %(<?xml version="1.0" encoding="UTF-8"?>\n<Error><Code>AccessDenied</Code><Message>Invalid according to Policy</Message><RequestId>1FECC4B719E426B1</RequestId><HostId>15+14lXt+HlF</HostId></Error>), status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
202
- expect_log("Attachment upload failed: AWS upload to https://itrp.s3.amazonaws.com/ for #{@key} failed: Invalid according to Policy", :error)
203
- expect(attachments(authentication).send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
193
+ data = {
194
+ subject: 'Foobar',
195
+ note: 'Foo [note_attachments: 2] Bar [note_attachments: 1]',
196
+ note_attachments: ['/tmp/doesnotexist.png']
197
+ }
198
+ expect { a.upload_attachments!(data) }.to raise_error(::Sdk4me::UploadFailed, 'Attachment upload failed: file does not exist: /tmp/doesnotexist.png')
204
199
  end
200
+ end
201
+ end
205
202
 
206
- it 'should report an error when 4me confirmation fails' do
207
- stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: 'OK', status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
208
- stub_request(:get, "https://api.4me.com/v1/s3_success?sig=99e82e8a046&key=#{@key}").with(credentials(authentication)).to_return(body: {message: 'oops!'}.to_json)
209
- expect_log('GET request to api.4me.com:443/v1/s3_success?sig=99e82e8a046&key=attachments%2F5%2Freqs%2F000%2F070%2F451%2Fzxxb4ot60xfd6sjg%2Fupload%2Etxt failed: oops!', :error)
210
- expect_log("Attachment upload failed: 4me confirmation s3_success?sig=99e82e8a046 for #{@key} failed: oops!", :error)
211
- expect(attachments(authentication).send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
212
- end
203
+ context :upload_attachment do
204
+ before(:each) do
205
+ resp = {
206
+ provider: 'local',
207
+ upload_uri: 'https://widget.example.com/attachments',
208
+ local: {
209
+ key: 'attachments/5/requests/000/000/777/abc/${filename}',
210
+ x_4me_expiration: '2020-11-01T23:59:59Z',
211
+ x_4me_signature: 'foobar'
212
+ }
213
+ }
214
+ stub_request(:get, 'https://api.4me.com/v1/attachments/storage').with(credentials(authentication)).to_return(status: 200, body: resp.to_json)
215
+ end
213
216
 
214
- it 'should raise an exception when AWS upload fails' do
215
- stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: %(<?xml version="1.0" encoding="UTF-8"?>\n<Error><Code>AccessDenied</Code><Message>Invalid according to Policy</Message><RequestId>1FECC4B719E426B1</RequestId><HostId>15+14lXt+HlF</HostId></Error>), status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
216
- message = "Attachment upload failed: AWS upload to https://itrp.s3.amazonaws.com/ for #{@key} failed: Invalid according to Policy"
217
- expect{ attachments(authentication).send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", true) }.to raise_error(::Sdk4me::UploadFailed, message)
218
- end
217
+ it 'should raise an error when the file could not be found' do
218
+ a = attachments(authentication, '/requests')
219
+ expect(@client).not_to receive(:send_file)
220
+ message = 'Attachment upload failed: file does not exist: /tmp/unknown_file'
221
+ expect_log(message, :error)
222
+ expect { a.send(:upload_attachment, '/tmp/unknown_file') }.to raise_error(::Sdk4me::UploadFailed, message)
219
223
  end
224
+ end
220
225
 
221
- context '4me' do
222
- before(:each) do
223
- @sdk4me_conf = {
224
- provider: 'local',
225
- upload_uri: 'https://api.4me.com/attachments',
226
- upload_path: 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/'
226
+ context :s3 do
227
+ before(:each) do
228
+ resp = {
229
+ provider: 's3',
230
+ upload_uri: 'https://example.s3-accelerate.amazonaws.com/',
231
+ s3: {
232
+ acl: 'private',
233
+ key: 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}',
234
+ policy: 'eydlgIH0=',
235
+ success_action_status: 201,
236
+ x_amz_algorithm: 'AWS4-HMAC-SHA256',
237
+ x_amz_credential: 'AKIATRO999Z9E9D2EQ7B/20201107/us-east-1/s3/aws4_request',
238
+ x_amz_date: '20201107T000000Z',
239
+ x_amz_server_side_encryption: 'AES256',
240
+ x_amz_signature: 'nbhdec4k='
227
241
  }
228
- @key_template = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}'
229
- @key = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/upload.txt'
230
-
231
- @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--"
232
- @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'}
233
- end
242
+ }
243
+ stub_request(:get, 'https://api.4me.com/v1/attachments/storage').with(credentials(authentication)).to_return(status: 200, body: resp.to_json)
244
+ end
234
245
 
235
- it 'should open a file from disk' do
236
- expect(attachments(authentication)).to receive(:upload_to_4me).with(@sdk4me_conf, @key_template, @key, kind_of(File))
237
- expect(attachments(authentication).send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
246
+ it 'should upload a file from disk' do
247
+ Tempfile.create('4me_attachments_spec.txt') do |file|
248
+ file << 'foobar'
249
+ file.flush
250
+
251
+ a = attachments(authentication, '/requests')
252
+
253
+ stub_request(:post, 'https://example.s3-accelerate.amazonaws.com/')
254
+ .with(
255
+ 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=\"acl\"\r\n\r\nprivate\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=\"policy\"\r\n\r\neydlgIH0=\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=\"x_amz_algorithm\"\r\n\r\nAWS4-HMAC-SHA256\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"x_amz_credential\"\r\n\r\nAKIATRO999Z9E9D2EQ7B/20201107/us-east-1/s3/aws4_request\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"x_amz_date\"\r\n\r\n20201107T000000Z\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=\"x_amz_signature\"\r\n\r\nnbhdec4k=\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{file.path}\"\r\nContent-Type: application/octet-stream\r\n\r\nfoobar\r\n--0123456789ABLEWASIEREISAWELBA9876543210--",
256
+ headers: {
257
+ 'Accept' => '*/*',
258
+ 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
259
+ 'Content-Type' => 'multipart/form-data; boundary=0123456789ABLEWASIEREISAWELBA9876543210',
260
+ '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 4me/#{Sdk4me::Client::VERSION}"
261
+ }
262
+ )
263
+ .to_return(status: 200, body: '', headers: {})
264
+
265
+ expect(a.send(:upload_attachment, file.path)).to eq({
266
+ key: "attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/#{File.basename(file.path)}",
267
+ filesize: 6
268
+ })
238
269
  end
270
+ end
239
271
 
240
- it 'should sent the upload to 4me' do
241
- stub_request(:post, 'https://api.4me.com/v1/attachments').with(credentials(authentication)).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {}.to_json)
242
- expect(attachments(authentication).send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
272
+ it 'should report an error when upload fails' do
273
+ Tempfile.create('4me_attachments_spec.txt') do |file|
274
+ file << 'foobar'
275
+ file.flush
276
+
277
+ a = attachments(authentication, '/requests')
278
+
279
+ stub_request(:post, 'https://example.s3-accelerate.amazonaws.com/')
280
+ .with(
281
+ 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=\"acl\"\r\n\r\nprivate\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=\"policy\"\r\n\r\neydlgIH0=\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=\"x_amz_algorithm\"\r\n\r\nAWS4-HMAC-SHA256\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"x_amz_credential\"\r\n\r\nAKIATRO999Z9E9D2EQ7B/20201107/us-east-1/s3/aws4_request\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"x_amz_date\"\r\n\r\n20201107T000000Z\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=\"x_amz_signature\"\r\n\r\nnbhdec4k=\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{file.path}\"\r\nContent-Type: application/octet-stream\r\n\r\nfoobar\r\n--0123456789ABLEWASIEREISAWELBA9876543210--",
282
+ headers: {
283
+ 'Accept' => '*/*',
284
+ 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
285
+ 'Content-Type' => 'multipart/form-data; boundary=0123456789ABLEWASIEREISAWELBA9876543210',
286
+ '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 4me/#{Sdk4me::Client::VERSION}"
287
+ }
288
+ )
289
+ .to_return(status: 400, body: '<Error><Message>Foo Bar Failure</Message></Error>', headers: {})
290
+
291
+ key = "attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/#{File.basename(file.path)}"
292
+ message = "Attachment upload failed: AWS S3 upload to https://example.s3-accelerate.amazonaws.com/ for #{key} failed: Foo Bar Failure"
293
+ # expect_log(message, :error)
294
+ expect { a.send(:upload_attachment, file.path) }.to raise_error(::Sdk4me::UploadFailed, message)
243
295
  end
296
+ end
297
+ end
244
298
 
245
- it 'should report an error when 4me upload fails' do
246
- stub_request(:post, 'https://api.4me.com/v1/attachments').with(credentials(authentication)).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
247
- expect_log('POST request to api.4me.com:443/v1/attachments failed: oops!', :error)
248
- expect_log("Attachment upload failed: 4me upload to https://api.4me.com/attachments for #{@key} failed: oops!", :error)
249
- expect(attachments(authentication).send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
250
- end
299
+ context '4me local' do
300
+ before(:each) do
301
+ resp = {
302
+ provider: 'local',
303
+ upload_uri: 'https://widget.example.com/attachments',
304
+ local: {
305
+ key: 'attachments/5/requests/000/000/777/abc/${filename}',
306
+ x_4me_expiration: '2020-11-01T23:59:59Z',
307
+ x_4me_signature: 'foobar'
308
+ }
309
+ }
310
+ stub_request(:get, 'https://api.4me.com/v1/attachments/storage').with(credentials(authentication)).to_return(status: 200, body: resp.to_json)
311
+ end
251
312
 
252
- it 'should raise an exception when 4me upload fails' do
253
- stub_request(:post, 'https://api.4me.com/v1/attachments').with(credentials(authentication)).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
254
- expect_log('POST request to api.4me.com:443/v1/attachments failed: oops!', :error)
255
- message = "Attachment upload failed: 4me upload to https://api.4me.com/attachments for #{@key} failed: oops!"
256
- expect{ attachments(authentication).send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", true) }.to raise_error(::Sdk4me::UploadFailed, message)
313
+ it 'should upload a file from disk' do
314
+ Tempfile.create('4me_attachments_spec.txt') do |file|
315
+ file << 'foobar'
316
+ file.flush
317
+
318
+ a = attachments(authentication, '/requests')
319
+
320
+ stub_request(:post, 'https://widget.example.com/attachments')
321
+ .with(
322
+ 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=\"key\"\r\n\r\nattachments/5/requests/000/000/777/abc/${filename}\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"x_4me_expiration\"\r\n\r\n2020-11-01T23:59:59Z\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"x_4me_signature\"\r\n\r\nfoobar\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{file.path}\"\r\nContent-Type: application/octet-stream\r\n\r\nfoobar\r\n--0123456789ABLEWASIEREISAWELBA9876543210--",
323
+ headers: {
324
+ 'Accept' => '*/*',
325
+ 'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
326
+ 'Authorization' => (authentication == :api_token ? 'Basic c2VjcmV0Ong=' : 'Bearer secret'),
327
+ 'Content-Type' => 'multipart/form-data; boundary=0123456789ABLEWASIEREISAWELBA9876543210',
328
+ '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 4me/#{Sdk4me::Client::VERSION}"
329
+ }
330
+ )
331
+ .to_return(status: 204, body: '', headers: {})
332
+
333
+ expect(a.send(:upload_attachment, file.path)).to eq({
334
+ key: "attachments/5/requests/000/000/777/abc/#{File.basename(file.path)}",
335
+ filesize: 6
336
+ })
257
337
  end
258
338
  end
259
-
260
339
  end
261
340
  end
262
341
  end