From 5503d94f92296a8edf9ecddd418b92f3f63833eb Mon Sep 17 00:00:00 2001 From: Marlin Pierce Date: Fri, 22 Dec 2023 17:17:49 -0500 Subject: [PATCH 1/4] Implement from TTD. --- lib/jira/resource/attachment.rb | 12 +++++++++ spec/jira/resource/attachment_spec.rb | 37 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/lib/jira/resource/attachment.rb b/lib/jira/resource/attachment.rb index b416e69b..35094685 100644 --- a/lib/jira/resource/attachment.rb +++ b/lib/jira/resource/attachment.rb @@ -1,4 +1,5 @@ require 'net/http/post/multipart' +require 'open-uri' module JIRA module Resource @@ -19,6 +20,17 @@ def self.meta(client) parse_json(response.body) end + def download_file(headers = {}, &block) + default_headers = client.options[:default_headers] + URI.open(content, default_headers.merge(headers), &block) + end + + def download_contents(headers = {}) + download_file(headers) do |file| + file.read + end + end + def save!(attrs, path = url) file = attrs['file'] || attrs[:file] # Keep supporting 'file' parameter as a string for backward compatibility mime_type = attrs[:mimeType] || 'application/binary' diff --git a/spec/jira/resource/attachment_spec.rb b/spec/jira/resource/attachment_spec.rb index 03e0c722..fde83563 100644 --- a/spec/jira/resource/attachment_spec.rb +++ b/spec/jira/resource/attachment_spec.rb @@ -59,6 +59,43 @@ end end + context 'there is an attachment on an issue' do + let(:client) do + JIRA::Client.new(username: 'username', password: 'password', auth_type: :basic, use_ssl: false ) + end + let(:attachment_file_contents) { 'file contents' } + let(:file_target) { double(read: :attachment_file_contents) } + let(:attachment_url) { "https:jirahost/secure/attachment/32323/myfile.txt" } + subject(:attachment) do + JIRA::Resource::Attachment.new( + client, + issue: JIRA::Resource::Issue.new(client), + attrs: { 'author' => { 'foo' => 'bar' }, 'content' => attachment_url } + ) + end + + describe '.download_file' do + it 'passes file object to block' do + expect(URI).to receive(:open).with(attachment_url, anything).and_yield(file_target) + + attachment.download_file do |file| + expect(file).to eq(file_target) + end + + end + end + + describe '.download_contents' do + it 'downloads the file contents as a string' do + expect(URI).to receive(:open).with(attachment_url, anything).and_return(attachment_file_contents) + + result_str = attachment.download_contents + + expect(result_str).to eq(attachment_file_contents) + end + end + end + describe '#save' do subject { attachment.save('file' => path_to_file) } let(:path_to_file) { './spec/mock_responses/issue.json' } From 5c0e59fe4ff8fc28935d2be2fffaeb8fd7fb2e50 Mon Sep 17 00:00:00 2001 From: Marlin Pierce Date: Fri, 22 Dec 2023 17:35:04 -0500 Subject: [PATCH 2/4] Documentation. --- lib/jira/resource/attachment.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/jira/resource/attachment.rb b/lib/jira/resource/attachment.rb index 35094685..5c94756c 100644 --- a/lib/jira/resource/attachment.rb +++ b/lib/jira/resource/attachment.rb @@ -20,11 +20,22 @@ def self.meta(client) parse_json(response.body) end + # Opens a file streaming the download of the attachment. + # @example Read the file contents + # download_file(headers) do |file| + # file.read + # end + # @param [Hash] headers Any additional headers to call Jira. + # @yield |file| + # @yieldparam [IO] file The IO object streaming the download. def download_file(headers = {}, &block) default_headers = client.options[:default_headers] URI.open(content, default_headers.merge(headers), &block) end + # Downloads the file contents as a string object. + # @param [Hash] headers Any additional headers to call Jira. + # @return [String,NilClass] The file contents. def download_contents(headers = {}) download_file(headers) do |file| file.read From 2979884e13c5706e62392b4223fb506b375d4f62 Mon Sep 17 00:00:00 2001 From: Marlin Pierce Date: Sun, 7 Jan 2024 12:11:13 -0500 Subject: [PATCH 3/4] Doc change to recommend against read into string. --- lib/jira/resource/attachment.rb | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/jira/resource/attachment.rb b/lib/jira/resource/attachment.rb index 5c94756c..886702ff 100644 --- a/lib/jira/resource/attachment.rb +++ b/lib/jira/resource/attachment.rb @@ -21,10 +21,22 @@ def self.meta(client) end # Opens a file streaming the download of the attachment. - # @example Read the file contents - # download_file(headers) do |file| - # file.read + # @example Write file contents to a file. + # File.open('some-filename', 'wb') do |output| + # download_file do |file| + # IO.copy_stream(file, output) + # end # end + # @example Stream file contents for an HTTP response. + # response.headers[ "Content-Type" ] = "application/octet-stream" + # download_file do |file| + # chunk = file.read(8000) + # while chunk.present? do + # response.stream.write(chunk) + # chunk = file.read(8000) + # end + # end + # response.stream.close # @param [Hash] headers Any additional headers to call Jira. # @yield |file| # @yieldparam [IO] file The IO object streaming the download. @@ -34,6 +46,11 @@ def download_file(headers = {}, &block) end # Downloads the file contents as a string object. + # + # Note that this reads the contents into a ruby string in memory. + # A file might be very large so it is recommend to avoid this unless you are certain about doing so. + # Use the download_file method instead and avoid calling the read method without a limit. + # # @param [Hash] headers Any additional headers to call Jira. # @return [String,NilClass] The file contents. def download_contents(headers = {}) From 3e0ddfb7d2473250e32c1d526545c9593cefbd72 Mon Sep 17 00:00:00 2001 From: Marlin Pierce Date: Fri, 5 Apr 2024 06:49:49 -0400 Subject: [PATCH 4/4] Resovle conflict. --- spec/jira/resource/attachment_spec.rb | 133 +++++++++++++++++--------- 1 file changed, 86 insertions(+), 47 deletions(-) diff --git a/spec/jira/resource/attachment_spec.rb b/spec/jira/resource/attachment_spec.rb index fde83563..13cf249d 100644 --- a/spec/jira/resource/attachment_spec.rb +++ b/spec/jira/resource/attachment_spec.rb @@ -96,79 +96,118 @@ end end - describe '#save' do - subject { attachment.save('file' => path_to_file) } - let(:path_to_file) { './spec/mock_responses/issue.json' } + context 'when there is a local file' do + let(:file_name) { 'short.txt' } + let(:file_size) { 11 } + let(:file_mime_type) { 'text/plain' } + let(:path_to_file) { "./spec/data/files/#{file_name}" } let(:response) do double( body: [ { "id": 10_001, "self": 'http://www.example.com/jira/rest/api/2.0/attachments/10000', - "filename": 'picture.jpg', + "filename": file_name, "created": '2017-07-19T12:23:06.572+0000', - "size": 23_123, - "mimeType": 'image/jpeg' + "size": file_size, + "mimeType": file_mime_type } ].to_json ) end let(:issue) { JIRA::Resource::Issue.new(client) } - before do - allow(client).to receive(:post_multipart).and_return(response) - end + describe '#save' do + subject { attachment.save('file' => path_to_file) } - it 'successfully update the attachment' do - subject + before do + allow(client).to receive(:post_multipart).and_return(response) + end - expect(attachment.filename).to eq 'picture.jpg' - expect(attachment.mimeType).to eq 'image/jpeg' - expect(attachment.size).to eq 23_123 - end - end + it 'successfully update the attachment' do + subject - describe '#save!' do - subject { attachment.save!('file' => path_to_file) } + expect(attachment.filename).to eq file_name + expect(attachment.mimeType).to eq file_mime_type + expect(attachment.size).to eq file_size + end + context 'when using custom client headers' do + subject(:bearer_attachment) do + JIRA::Resource::Attachment.new( + bearer_client, + issue: JIRA::Resource::Issue.new(bearer_client), + attrs: { 'author' => { 'foo' => 'bar' } } + ) + end + let(:default_headers_given) { { 'authorization' => "Bearer 83CF8B609DE60036A8277BD0E96135751BBC07EB234256D4B65B893360651BF2" } } + let(:bearer_client) do + JIRA::Client.new(username: 'username', password: 'password', auth_type: :basic, use_ssl: false, + default_headers: default_headers_given ) + end + let(:merged_headers) do + {"Accept"=>"application/json", "X-Atlassian-Token"=>"nocheck"}.merge(default_headers_given) + end - let(:path_to_file) { './spec/mock_responses/issue.json' } - let(:response) do - double( - body: [ - { - "id": 10_001, - "self": 'http://www.example.com/jira/rest/api/2.0/attachments/10000', - "filename": 'picture.jpg', - "created": '2017-07-19T12:23:06.572+0000', - "size": 23_123, - "mimeType": 'image/jpeg' - } - ].to_json - ) - end - let(:issue) { JIRA::Resource::Issue.new(client) } + it 'passes the custom headers' do + expect(bearer_client.request_client).to receive(:request_multipart).with(anything, anything, merged_headers).and_return(response) - before do - allow(client).to receive(:post_multipart).and_return(response) - end + bearer_attachment.save('file' => path_to_file) - it 'successfully update the attachment' do - subject + end + end - expect(attachment.filename).to eq 'picture.jpg' - expect(attachment.mimeType).to eq 'image/jpeg' - expect(attachment.size).to eq 23_123 end - context 'when passing in a symbol as file key' do - subject { attachment.save!(file: path_to_file) } + describe '#save!' do + subject { attachment.save!('file' => path_to_file) } + + before do + allow(client).to receive(:post_multipart).and_return(response) + end it 'successfully update the attachment' do subject - expect(attachment.filename).to eq 'picture.jpg' - expect(attachment.mimeType).to eq 'image/jpeg' - expect(attachment.size).to eq 23_123 + expect(attachment.filename).to eq file_name + expect(attachment.mimeType).to eq file_mime_type + expect(attachment.size).to eq file_size + end + + context 'when passing in a symbol as file key' do + subject { attachment.save!(file: path_to_file) } + + it 'successfully update the attachment' do + subject + + expect(attachment.filename).to eq file_name + expect(attachment.mimeType).to eq file_mime_type + expect(attachment.size).to eq file_size + end + end + + context 'when using custom client headers' do + subject(:bearer_attachment) do + JIRA::Resource::Attachment.new( + bearer_client, + issue: JIRA::Resource::Issue.new(bearer_client), + attrs: { 'author' => { 'foo' => 'bar' } } + ) + end + let(:default_headers_given) { { 'authorization' => "Bearer 83CF8B609DE60036A8277BD0E96135751BBC07EB234256D4B65B893360651BF2" } } + let(:bearer_client) do + JIRA::Client.new(username: 'username', password: 'password', auth_type: :basic, use_ssl: false, + default_headers: default_headers_given ) + end + let(:merged_headers) do + {"Accept"=>"application/json", "X-Atlassian-Token"=>"nocheck"}.merge(default_headers_given) + end + + it 'passes the custom headers' do + expect(bearer_client.request_client).to receive(:request_multipart).with(anything, anything, merged_headers).and_return(response) + + bearer_attachment.save!('file' => path_to_file) + + end end end end