diff --git a/confluence-api-client.gemspec b/confluence-api-client.gemspec index 8a8490f..e0775fb 100644 --- a/confluence-api-client.gemspec +++ b/confluence-api-client.gemspec @@ -22,9 +22,13 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_development_dependency "bundler", "~> 1.8" + spec.add_development_dependency "bundler", "~> 2.1" spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rspec-core" + spec.add_development_dependency "rspec-expectations" + spec.add_development_dependency "rspec-mocks" spec.add_runtime_dependency "json" spec.add_runtime_dependency "faraday" + spec.add_runtime_dependency "mime-types" end diff --git a/lib/confluence/api/client.rb b/lib/confluence/api/client.rb index 128b68b..555dc27 100644 --- a/lib/confluence/api/client.rb +++ b/lib/confluence/api/client.rb @@ -1,22 +1,18 @@ require "confluence/api/client/version" require 'json' require 'faraday' +require 'securerandom' +require 'mime/types' module Confluence module Api class Client - attr_accessor :user, :pass, :url, :conn + attr_accessor :user, :pass, :url def initialize(user, pass, url) self.user = user self.pass = pass self.url = url - self.conn = Faraday.new(url: url) do |faraday| - faraday.request :url_encoded # form-encode POST params - # faraday.response :logger # log requests to STDOUT - faraday.adapter Faraday.default_adapter # make requests with Net::HTTP - faraday.basic_auth(self.user, self.pass) - end end def get(params) @@ -47,6 +43,66 @@ def update(id, params) JSON.parse(response.body) end + def create_attachment(page_id, file_info, comment=nil) + sep = SecureRandom.hex(8) + file = resolve_file_info_from(file_info) + boundary = "-----------------------#{ sep }" + + file_section = [ + "Content-Disposition: form-data; name=\"file\"; filename=\"#{ file[:name] }\"", + "Content-Type: #{ file[:type] }", + "", + file[:content], + "" + ].join("\r\n") + + content = "--#{ boundary }\r\n" + content << file_section + if comment + content << "--#{ boundary }\r\n" + comment_section = [ + "Content-Disposition: form-data; name=\"comment\"", + "", + comment, + "" + ].join("\r\n") + content << comment_section + end + content << "--#{ boundary }--\r\n" + + response = conn.post do |request| + request.headers['content-type'] = "multipart/form-data; boundary=#{ boundary }" + request.headers['X-Atlassian-Token'] = 'nocheck' + url = "rest/api/content/#{ page_id }/child/attachment" + request.url url + request.body = content + end + + [response.status == 200 ? :ok : :error, JSON.parse(response.body)] + end + + private + + def resolve_file_info_from file_info + return file_info if file_info.kind_of?(Hash) + + file_name = file_info.split('/').last + + { + name: file_name, + type: MIME::Types.type_for(file_name).first, + content: File.open(file_info).read + } + end + + def conn + @conn ||= Faraday.new(url: url) do |faraday| + faraday.request :url_encoded # form-encode POST params + # faraday.response :logger # log requests to STDOUT + faraday.adapter Faraday.default_adapter # make requests with Net::HTTP + faraday.basic_auth(user, pass) + end + end end end end diff --git a/spec/confluence/api/client_spec.rb b/spec/confluence/api/client_spec.rb index cf2387c..f664bbb 100644 --- a/spec/confluence/api/client_spec.rb +++ b/spec/confluence/api/client_spec.rb @@ -5,7 +5,114 @@ expect(Confluence::Api::Client::VERSION).not_to be nil end - it 'does something useful' do - expect(false).to eq(true) + describe 'instance methods' do + subject(:instance) { described_class.new(user, pass, url) } + let(:user) { 'confluence-user-identifier' } + let(:pass) { 'confluence-api-key' } + let(:url) { 'confluence-url' } + + let(:dummy_connection) { double(:connection) } + let(:dummy_request) { double(:request, headers: {}) } + + describe '#create_attachment' do + let(:page_id) { 1234 } + let(:random_hex) { 'deadbeefdeadbeef' } + let(:comment) { nil } + + before :each do + allow(instance).to receive(:conn).and_return(dummy_connection) + allow(dummy_connection).to receive(:post).and_yield(dummy_request) + allow(SecureRandom).to receive(:hex).with(8).and_return(random_hex) + allow(dummy_request).to receive(:url) + allow(dummy_request).to receive(:body=) + + if comment.nil? + instance.create_attachment(page_id, file_info) + else + instance.create_attachment(page_id, file_info, comment) + end + end + + context 'using a filename to identify the file' do + let(:file_info) { File.expand_path('../../../fixtures/example.txt', __FILE__) } + let(:expected_content) do + <<-TEXT +-------------------------deadbeefdeadbeef\r +Content-Disposition: form-data; name="file"; filename="example.txt"\r +Content-Type: text/plain\r +\r +This is a simple example text file.\r +-------------------------deadbeefdeadbeef--\r + TEXT + end + + it 'submits what is expected' do + expect(dummy_connection).to have_received(:post) + expect(dummy_request.headers).to eq({ + 'content-type' => "multipart/form-data; boundary=-----------------------#{ random_hex }", + 'X-Atlassian-Token' => 'nocheck' + }) + expect(dummy_request).to have_received(:url).with("rest/api/content/#{ page_id }/child/attachment") + expect(dummy_request).to have_received(:body=).with expected_content + end + + context 'and it includes a comment' do + let(:comment) { 'Silly Little Comment' } + + let(:expected_content) do + <<-TEXT +-------------------------deadbeefdeadbeef\r +Content-Disposition: form-data; name="file"; filename="example.txt"\r +Content-Type: text/plain\r +\r +This is a simple example text file.\r +-------------------------deadbeefdeadbeef\r +Content-Disposition: form-data; name="comment"\r +\r +Silly Little Comment\r +-------------------------deadbeefdeadbeef--\r + TEXT + end + + it 'submits what is expected' do + expect(dummy_request).to have_received(:body=).with expected_content + end + end + end + + context 'using a hash to identify the file' do + let(:file_info) do + { + name: 'sample.txt', + type: 'text/plain', + content: <<-CONTENT +This is some lovely LOVELY content. + CONTENT + } + end + + let(:expected_content) do + <<-TEXT +-------------------------deadbeefdeadbeef\r +Content-Disposition: form-data; name="file"; filename="sample.txt"\r +Content-Type: text/plain\r +\r +This is some lovely LOVELY content. +\r +-------------------------deadbeefdeadbeef--\r + TEXT + end + + it 'submits what is expected' do + expect(dummy_connection).to have_received(:post) + expect(dummy_request.headers).to eq({ + 'content-type' => "multipart/form-data; boundary=-----------------------#{ random_hex }", + 'X-Atlassian-Token' => 'nocheck' + }) + expect(dummy_request).to have_received(:url).with("rest/api/content/#{ page_id }/child/attachment") + expect(dummy_request).to have_received(:body=).with expected_content + end + end + end end end diff --git a/spec/fixtures/example.csv b/spec/fixtures/example.csv new file mode 100644 index 0000000..7fba8c5 --- /dev/null +++ b/spec/fixtures/example.csv @@ -0,0 +1,4 @@ +First,Second,Third +a,b,c +1,2,3 +x,y,z \ No newline at end of file diff --git a/spec/fixtures/example.txt b/spec/fixtures/example.txt new file mode 100644 index 0000000..7be5f07 --- /dev/null +++ b/spec/fixtures/example.txt @@ -0,0 +1 @@ +This is a simple example text file. \ No newline at end of file