4me-sdk 1.1.6 → 2.0.0.pre.rc.2

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.
@@ -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.1.6'
3
+ VERSION = '2.0.0-rc.2'.freeze
4
4
  end
5
5
  end
@@ -1,178 +1,342 @@
1
1
  require 'spec_helper'
2
+ require 'tempfile'
2
3
 
3
4
  describe Sdk4me::Attachments do
4
-
5
- before(:each) do
6
- @client = Sdk4me::Client.new(api_token: 'secret', max_retry_time: -1)
7
- @attachments = Sdk4me::Attachments.new(@client)
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)
8
12
  end
9
13
 
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
14
+ def credentials(authentication)
15
+ if authentication == :api_token
16
+ { basic_auth: %w[secret x] }
17
+ else
18
+ { headers: { 'Authorization' => 'Bearer secret' } }
13
19
  end
20
+ end
14
21
 
15
- it 'should not do anything when :attachments is nil' do
16
- expect(@attachments.upload_attachments!('/requests', {attachments: nil})).to be_nil
17
- end
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 })
30
+ end
18
31
 
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
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 })
36
+ end
23
37
 
24
- it 'should show a error if no attachment may be uploaded' do
25
- stub_request(:get, 'https://api.4me.com/v1/sites/1?attachment_upload_token=true').with(basic_auth: ['secret', 'x']).to_return(body: {name: 'site 1'}.to_json)
26
- expect_log('Attachments not allowed for /sites/1', :error)
27
- expect(@attachments.upload_attachments!('/sites/1', {attachments: ['file1.png']})).to be_nil
28
- end
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] })
43
+ end
29
44
 
30
- it 'should raise an exception if no attachment may be uploaded' do
31
- stub_request(:get, 'https://api.4me.com/v1/sites/1?attachment_upload_token=true').with(basic_auth: ['secret', 'x']).to_return(body: {name: 'site 1'}.to_json)
32
- message = 'Attachments not allowed for /sites/1'
33
- expect{ @attachments.upload_attachments!('/sites/1', {attachments: ['file1.png'], attachments_exception: true}) }.to raise_error(::Sdk4me::UploadFailed, message)
34
- end
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')
52
+ end
35
53
 
36
- it 'should add /new to the path for new records' do
37
- stub_request(:get, 'https://api.4me.com/v1/sites/new?attachment_upload_token=true').with(basic_auth: ['secret', 'x']).to_return(body: {missing: 'storage'}.to_json)
38
- expect_log('Attachments not allowed for /sites', :error)
39
- expect(@attachments.upload_attachments!('/sites', {attachments: ['file1.png']})).to be_nil
40
- end
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)
41
66
 
42
- [ [:requests, :note],
43
- [:problems, :note],
44
- [:contracts, :remarks],
45
- [:cis, :remarks],
46
- [:flsas, :remarks],
47
- [:slas, :remarks],
48
- [:service_instances, :remarks],
49
- [:service_offerings, :summary],
50
- [:any_other_model, :note]].each do |model, attribute|
51
-
52
- it "should replace :attachments with :#{attribute}_attachments after upload at /#{model}" do
53
- stub_request(:get, "https://api.4me.com/v1/#{model}/new?attachment_upload_token=true").with(basic_auth: ['secret', 'x']).to_return(body: {storage_upload: 'conf'}.to_json)
54
- expect(@attachments).to receive(:upload_attachment).with('conf', 'file1.png', false).ordered{ 'uploaded file1.png' }
55
- expect(@attachments).to receive(:upload_attachment).with('conf', 'file2.zip', false).ordered{ 'uploaded file2.zip' }
56
- data = {leave: 'me alone', attachments: %w(file1.png file2.zip)}
57
- @attachments.upload_attachments!("/#{model}", data)
58
- expect(data[:attachments]).to be_nil
59
- expect(data[:leave]).to eq('me alone')
60
- expect(data[:"#{attribute}_attachments"]).to eq(['uploaded file1.png', 'uploaded file2.zip'].to_json)
61
- end
62
- end
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 } })
63
69
 
64
- it 'should set raise_exception flag to true when :attachments_exception is set' do
65
- stub_request(:get, 'https://api.4me.com/v1/requests/new?attachment_upload_token=true').with(basic_auth: ['secret', 'x']).to_return(body: {storage_upload: 'conf'}.to_json)
66
- expect(@attachments).to receive(:upload_attachment).with('conf', 'file1.png', true).ordered{ 'uploaded file1.png' }
67
- data = {leave: 'me alone', attachments: 'file1.png', attachments_exception: true}
68
- @attachments.upload_attachments!('/requests', data)
69
- expect(data[:attachments]).to be_nil
70
- expect(data[:attachments_exception]).to be_nil
71
- expect(data[:leave]).to eq('me alone')
72
- expect(data[:note_attachments]).to eq(['uploaded file1.png'].to_json)
73
- end
74
- end
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
+ ] })
76
+ end
77
+ end
75
78
 
76
- context 'upload_attachment' do
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]' })
86
+ end
77
87
 
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
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]' })
94
+ end
82
95
 
83
- it 'should raise an exception when the file could not be found' do
84
- message = 'Attachment upload failed: file does not exist: unknown_file'
85
- expect{ @attachments.send(:upload_attachment, nil, 'unknown_file', true) }.to raise_error(::Sdk4me::UploadFailed, message)
86
- end
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')
106
+ end
87
107
 
88
- context 'aws' do
89
- before(:each) do
90
- @aws_conf = {
91
- provider: 'aws',
92
- upload_uri: 'https://itrp.s3.amazonaws.com/',
93
- access_key: 'AKIA6RYQ',
94
- success_url: 'https://mycompany.4me.com/s3_success?sig=99e82e8a046',
95
- policy: 'eydlgIH0=',
96
- signature: 'nbhdec4k=',
97
- upload_path: 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/'
98
- }
99
- @key_template = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}'
100
- @key = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/upload.txt'
101
-
102
- @multi_part_body = "--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"Content-Type\"\r\n\r\napplication/octet-stream\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"x-amz-server-side-encryption\"\r\n\r\nAES256\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\nattachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"AWSAccessKeyId\"\r\n\r\nAKIA6RYQ\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"acl\"\r\n\r\nprivate\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"signature\"\r\n\r\nnbhdec4k=\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"policy\"\r\n\r\neydlgIH0=\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{@fixture_dir}/upload.txt\"\r\nContent-Type: text/plain\r\n\r\ncontent\r\n--0123456789ABLEWASIEREISAWELBA9876543210--"
103
- @multi_part_headers = {'Accept'=>'*/*', 'Content-Type'=>'multipart/form-data; boundary=0123456789ABLEWASIEREISAWELBA9876543210', 'User-Agent'=>'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6'}
104
- end
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)
105
120
 
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
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 } })
110
123
 
111
- it 'should sent the upload to AWS' do
112
- stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: 'OK', status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
113
- stub_request(:get, "https://api.4me.com/v1/s3_success?sig=99e82e8a046&key=#{@key}").with(basic_auth: ['secret', 'x']).to_return(body: {}.to_json)
114
- expect(@attachments.send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
115
- end
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)
116
130
 
117
- it 'should report an error when AWS upload fails' do
118
- stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: %(<?xml version="1.0" encoding="UTF-8"?>\n<Error><Code>AccessDenied</Code><Message>Invalid according to Policy</Message><RequestId>1FECC4B719E426B1</RequestId><HostId>15+14lXt+HlF</HostId></Error>), status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
119
- expect_log("Attachment upload failed: AWS upload to https://itrp.s3.amazonaws.com/ for #{@key} failed: Invalid according to Policy", :error)
120
- expect(@attachments.send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
121
- end
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
+ })
139
+ end
140
+ end
122
141
 
123
- it 'should report an error when 4me confirmation fails' do
124
- stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: 'OK', status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
125
- stub_request(:get, "https://api.4me.com/v1/s3_success?sig=99e82e8a046&key=#{@key}").with(basic_auth: ['secret', 'x']).to_return(body: {message: 'oops!'}.to_json)
126
- expect_log('Request failed: oops!', :error)
127
- expect_log("Attachment upload failed: 4me confirmation s3_success?sig=99e82e8a046 for #{@key} failed: oops!", :error)
128
- expect(@attachments.send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
129
- end
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)
155
+
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 } })
159
+
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']
164
+ }
165
+ a.upload_attachments!(data)
130
166
 
131
- it 'should raise an exception when AWS upload fails' do
132
- stub_request(:post, 'https://itrp.s3.amazonaws.com/').with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: %(<?xml version="1.0" encoding="UTF-8"?>\n<Error><Code>AccessDenied</Code><Message>Invalid according to Policy</Message><RequestId>1FECC4B719E426B1</RequestId><HostId>15+14lXt+HlF</HostId></Error>), status: 303, headers: {'Location' => 'https://mycompany.4me.com/s3_success?sig=99e82e8a046'})
133
- message = "Attachment upload failed: AWS upload to https://itrp.s3.amazonaws.com/ for #{@key} failed: Invalid according to Policy"
134
- expect{ @attachments.send(:upload_attachment, @aws_conf, "#{@fixture_dir}/upload.txt", true) }.to raise_error(::Sdk4me::UploadFailed, message)
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
+ })
176
+ end
177
+
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)
190
+
191
+ expect_log('Attachment upload failed: file does not exist: /tmp/doesnotexist.png', :error)
192
+
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')
199
+ end
200
+ end
135
201
  end
136
- end
137
202
 
138
- context '4me' do
139
- before(:each) do
140
- @sdk4me_conf = {
203
+ context :upload_attachment do
204
+ before(:each) do
205
+ resp = {
141
206
  provider: 'local',
142
- upload_uri: 'https://api.4me.com/attachments',
143
- upload_path: 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/'
144
- }
145
- @key_template = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}'
146
- @key = 'attachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/upload.txt'
147
-
148
- @multi_part_body = "--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"Content-Type\"\r\n\r\napplication/octet-stream\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{@spec_dir}/support/fixtures/upload.txt\"\r\nContent-Type: text/plain\r\n\r\ncontent\r\n--0123456789ABLEWASIEREISAWELBA9876543210\r\nContent-Disposition: form-data; name=\"key\"\r\n\r\nattachments/5/reqs/000/070/451/zxxb4ot60xfd6sjg/${filename}\r\n--0123456789ABLEWASIEREISAWELBA9876543210--"
149
- @multi_part_headers = {'Accept'=>'*/*', 'Content-Type'=>'multipart/form-data; boundary=0123456789ABLEWASIEREISAWELBA9876543210', 'User-Agent'=>'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6'}
150
- end
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
151
216
 
152
- it 'should open a file from disk' do
153
- expect(@attachments).to receive(:upload_to_4me).with(@sdk4me_conf, @key_template, @key, kind_of(File))
154
- expect(@attachments.send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
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)
223
+ end
155
224
  end
156
225
 
157
- it 'should sent the upload to 4me' do
158
- stub_request(:post, 'https://api.4me.com/v1/attachments').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {}.to_json)
159
- expect(@attachments.send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", false)).to eq({key: @key, filesize: 7})
160
- end
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='
241
+ }
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
245
+
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, headers: {}, body: %(<?xml version="1.0" encoding="UTF-8"?>\n<PostResponse><Location>foo</Location><Bucket>example</Bucket><Key>attachments/5/zxxb4ot60xfd6sjg/s3test.txt</Key><ETag>"bar"</ETag></PostResponse>))
264
+
265
+ expect(a.send(:upload_attachment, file.path)).to eq({
266
+ key: 'attachments/5/zxxb4ot60xfd6sjg/s3test.txt',
267
+ filesize: 6
268
+ })
269
+ end
270
+ end
161
271
 
162
- it 'should report an error when 4me upload fails' do
163
- stub_request(:post, 'https://api.4me.com/v1/attachments').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
164
- expect_log('Request failed: oops!', :error)
165
- expect_log("Attachment upload failed: 4me upload to https://api.4me.com/attachments for #{@key} failed: oops!", :error)
166
- expect(@attachments.send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", false)).to be_nil
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)
295
+ end
296
+ end
167
297
  end
168
298
 
169
- it 'should raise an exception when 4me upload fails' do
170
- stub_request(:post, 'https://api.4me.com/v1/attachments').with(basic_auth: ['secret', 'x']).with(body: @multi_part_body, headers: @multi_part_headers).to_return(body: {message: 'oops!'}.to_json)
171
- expect_log('Request failed: oops!', :error)
172
- message = "Attachment upload failed: 4me upload to https://api.4me.com/attachments for #{@key} failed: oops!"
173
- expect{ @attachments.send(:upload_attachment, @sdk4me_conf, "#{@fixture_dir}/upload.txt", true) }.to raise_error(::Sdk4me::UploadFailed, message)
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
312
+
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
+ })
337
+ end
338
+ end
174
339
  end
175
340
  end
176
-
177
341
  end
178
342
  end