jira-ruby 1.6.0 → 2.3.0

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +3 -2
  4. data/README.md +40 -11
  5. data/Rakefile +2 -2
  6. data/example.rb +8 -0
  7. data/jira-ruby.gemspec +1 -2
  8. data/lib/jira/base.rb +1 -1
  9. data/lib/jira/client.rb +85 -12
  10. data/lib/jira/http_client.rb +29 -13
  11. data/lib/jira/http_error.rb +1 -1
  12. data/lib/jira/jwt_client.rb +67 -0
  13. data/lib/jira/oauth_client.rb +18 -6
  14. data/lib/jira/request_client.rb +15 -3
  15. data/lib/jira/resource/attachment.rb +19 -14
  16. data/lib/jira/resource/board.rb +8 -1
  17. data/lib/jira/resource/board_configuration.rb +9 -0
  18. data/lib/jira/resource/issue_picker_suggestions.rb +24 -0
  19. data/lib/jira/resource/issue_picker_suggestions_issue.rb +10 -0
  20. data/lib/jira/resource/sprint.rb +12 -8
  21. data/lib/jira/resource/suggested_issue.rb +9 -0
  22. data/lib/jira/resource/user.rb +1 -1
  23. data/lib/jira/resource/watcher.rb +7 -0
  24. data/lib/jira/version.rb +1 -1
  25. data/lib/jira-ruby.rb +6 -0
  26. data/spec/integration/user_spec.rb +3 -3
  27. data/spec/integration/watcher_spec.rb +15 -6
  28. data/spec/jira/base_spec.rb +12 -0
  29. data/spec/jira/client_spec.rb +70 -0
  30. data/spec/jira/http_client_spec.rb +137 -8
  31. data/spec/jira/jwt_uri_builder_spec.rb +59 -0
  32. data/spec/jira/oauth_client_spec.rb +47 -10
  33. data/spec/jira/request_client_spec.rb +37 -10
  34. data/spec/jira/resource/attachment_spec.rb +79 -22
  35. data/spec/jira/resource/board_spec.rb +50 -1
  36. data/spec/jira/resource/issue_picker_suggestions_spec.rb +79 -0
  37. data/spec/jira/resource/issue_spec.rb +1 -1
  38. data/spec/jira/resource/jira_picker_suggestions_issue_spec.rb +18 -0
  39. data/spec/jira/resource/sprint_spec.rb +23 -11
  40. metadata +32 -8
@@ -19,27 +19,32 @@ module JIRA
19
19
  parse_json(response.body)
20
20
  end
21
21
 
22
- def save!(attrs)
23
- headers = { 'X-Atlassian-Token' => 'nocheck' }
24
- data = { 'file' => UploadIO.new(attrs['file'], 'application/binary', attrs['file']) }
25
-
26
- request = Net::HTTP::Post::Multipart.new url, data, headers
27
- request.basic_auth(client.request_client.options[:username],
28
- client.request_client.options[:password])
22
+ def save!(attrs, path = url)
23
+ file = attrs['file'] || attrs[:file] # Keep supporting 'file' parameter as a string for backward compatibility
24
+ mime_type = attrs[:mimeType] || 'application/binary'
29
25
 
30
- response = client.request_client.basic_auth_http_conn.request(request)
26
+ headers = { 'X-Atlassian-Token' => 'nocheck' }
27
+ data = { 'file' => UploadIO.new(file, mime_type, file) }
31
28
 
32
- set_attrs(attrs, false)
33
- unless response.body.nil? || response.body.length < 2
34
- json = self.class.parse_json(response.body)
35
- attachment = json[0]
29
+ response = client.post_multipart(path, data , headers)
36
30
 
37
- set_attrs(attachment)
38
- end
31
+ set_attributes(attrs, response)
39
32
 
40
33
  @expanded = false
41
34
  true
42
35
  end
36
+
37
+ private
38
+
39
+ def set_attributes(attributes, response)
40
+ set_attrs(attributes, false)
41
+ return if response.body.nil? || response.body.length < 2
42
+
43
+ json = self.class.parse_json(response.body)
44
+ attachment = json[0]
45
+
46
+ set_attrs(attachment)
47
+ end
43
48
  end
44
49
  end
45
50
  end
@@ -31,7 +31,7 @@ module JIRA
31
31
  end
32
32
 
33
33
  def issues(params = {})
34
- path = path_base(client) + "/board/#{id}/issue?"
34
+ path = path_base(client) + "/board/#{id}/issue"
35
35
  response = client.get(url_with_query_params(path, params))
36
36
  json = self.class.parse_json(response.body)
37
37
  results = json['issues']
@@ -46,6 +46,13 @@ module JIRA
46
46
  results.map { |issue| client.Issue.build(issue) }
47
47
  end
48
48
 
49
+ def configuration(params = {})
50
+ path = path_base(client) + "/board/#{id}/configuration"
51
+ response = client.get(url_with_query_params(path, params))
52
+ json = self.class.parse_json(response.body)
53
+ client.BoardConfiguration.build(json)
54
+ end
55
+
49
56
  # options
50
57
  # - state ~ future, active, closed, you can define multiple states separated by commas, e.g. state=active,closed
51
58
  # - maxResults ~ default: 50 (JIRA API), 1000 (this library)
@@ -0,0 +1,9 @@
1
+ module JIRA
2
+ module Resource
3
+ class BoardConfigurationFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class BoardConfiguration < JIRA::Base
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ module JIRA
2
+ module Resource
3
+ class IssuePickerSuggestionsFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class IssuePickerSuggestions < JIRA::Base
7
+ has_many :sections, class: JIRA::Resource::IssuePickerSuggestionsIssue
8
+
9
+ def self.all(client, query = '', options = { current_jql: nil, current_issue_key: nil, current_project_id: nil, show_sub_tasks: nil, show_sub_tasks_parent: nil })
10
+ url = client.options[:rest_base_path] + "/issue/picker?query=#{CGI.escape(query)}"
11
+
12
+ url << "&currentJQL=#{CGI.escape(options[:current_jql])}" if options[:current_jql]
13
+ url << "&currentIssueKey=#{CGI.escape(options[:current_issue_key])}" if options[:current_issue_key]
14
+ url << "&currentProjectId=#{CGI.escape(options[:current_project_id])}" if options[:current_project_id]
15
+ url << "&showSubTasks=#{options[:show_sub_tasks]}" if options[:show_sub_tasks]
16
+ url << "&showSubTaskParent=#{options[:show_sub_task_parent]}" if options[:show_sub_task_parent]
17
+
18
+ response = client.get(url)
19
+ json = parse_json(response.body)
20
+ client.IssuePickerSuggestions.build(json)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ module JIRA
2
+ module Resource
3
+ class IssuePickerSuggestionsIssueFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class IssuePickerSuggestionsIssue < JIRA::Base
7
+ has_many :issues, class: JIRA::Resource::SuggestedIssue
8
+ end
9
+ end
10
+ end
@@ -5,7 +5,7 @@ module JIRA
5
5
 
6
6
  class Sprint < JIRA::Base
7
7
  def self.find(client, key)
8
- response = client.get("#{client.options[:site]}/rest/agile/1.0/sprint/#{key}")
8
+ response = client.get(agile_path(client, key))
9
9
  json = parse_json(response.body)
10
10
  client.Sprint.build(json)
11
11
  end
@@ -19,7 +19,7 @@ module JIRA
19
19
 
20
20
  def add_issue(issue)
21
21
  request_body = { issues: [issue.id] }.to_json
22
- response = client.post(client.options[:site] + "/rest/agile/1.0/sprint/#{id}/issue", request_body)
22
+ response = client.post("#{agile_path}/issue", request_body)
23
23
  true
24
24
  end
25
25
 
@@ -47,8 +47,8 @@ module JIRA
47
47
  end
48
48
 
49
49
  def get_sprint_details
50
- search_url = client.options[:site] + '/rest/greenhopper/1.0/rapid/charts/sprintreport?rapidViewId=' +
51
- rapidview_id.to_s + '&sprintId=' + id.to_s
50
+ search_url =
51
+ "#{client.options[:site]}#{client.options[:client_path]}/rest/greenhopper/1.0/rapid/charts/sprintreport?rapidViewId=#{rapidview_id}&sprintId=#{id}"
52
52
  begin
53
53
  response = client.get(search_url)
54
54
  rescue StandardError
@@ -76,12 +76,12 @@ module JIRA
76
76
 
77
77
  def save(attrs = {}, _path = nil)
78
78
  attrs = @attrs if attrs.empty?
79
- super(attrs, agile_url)
79
+ super(attrs, agile_path)
80
80
  end
81
81
 
82
82
  def save!(attrs = {}, _path = nil)
83
83
  attrs = @attrs if attrs.empty?
84
- super(attrs, agile_url)
84
+ super(attrs, agile_path)
85
85
  end
86
86
 
87
87
  # WORK IN PROGRESS
@@ -93,8 +93,12 @@ module JIRA
93
93
 
94
94
  private
95
95
 
96
- def agile_url
97
- "#{client.options[:site]}/rest/agile/1.0/sprint/#{id}"
96
+ def agile_path
97
+ self.class.agile_path(client, id)
98
+ end
99
+
100
+ def self.agile_path(client, key)
101
+ "#{client.options[:context_path]}/rest/agile/1.0/sprint/#{key}"
98
102
  end
99
103
  end
100
104
  end
@@ -0,0 +1,9 @@
1
+ module JIRA
2
+ module Resource
3
+ class SuggestedIssueFactory < JIRA::BaseFactory # :nodoc:
4
+ end
5
+
6
+ class SuggestedIssue < JIRA::Base
7
+ end
8
+ end
9
+ end
@@ -18,7 +18,7 @@ module JIRA
18
18
 
19
19
  # Cannot retrieve more than 1,000 users through the api, please see: https://jira.atlassian.com/browse/JRASERVER-65089
20
20
  def self.all(client)
21
- response = client.get("/rest/api/2/user/search?username=_&maxResults=#{MAX_RESULTS}")
21
+ response = client.get("/rest/api/2/users/search?username=_&maxResults=#{MAX_RESULTS}")
22
22
  all_users = JSON.parse(response.body)
23
23
 
24
24
  all_users.flatten.uniq.map do |user|
@@ -23,6 +23,13 @@ module JIRA
23
23
  issue.watchers.build(watcher)
24
24
  end
25
25
  end
26
+
27
+ def save!(user_id, path = nil)
28
+ path ||= new_record? ? url : patched_url
29
+ response = client.post(path, user_id.to_json)
30
+ true
31
+ end
32
+
26
33
  end
27
34
  end
28
35
  end
data/lib/jira/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module JIRA
2
- VERSION = '1.6.0'.freeze
2
+ VERSION = '2.3.0'.freeze
3
3
  end
data/lib/jira-ruby.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  $LOAD_PATH << __dir__
2
2
 
3
+ require 'active_support'
3
4
  require 'active_support/inflector'
4
5
  ActiveSupport::Inflector.inflections do |inflector|
5
6
  inflector.singular /status$/, 'status'
@@ -25,6 +26,9 @@ require 'jira/resource/worklog'
25
26
  require 'jira/resource/applinks'
26
27
  require 'jira/resource/issuelinktype'
27
28
  require 'jira/resource/issuelink'
29
+ require 'jira/resource/suggested_issue'
30
+ require 'jira/resource/issue_picker_suggestions_issue'
31
+ require 'jira/resource/issue_picker_suggestions'
28
32
  require 'jira/resource/remotelink'
29
33
  require 'jira/resource/sprint'
30
34
  require 'jira/resource/sprint_report'
@@ -38,10 +42,12 @@ require 'jira/resource/createmeta'
38
42
  require 'jira/resource/webhook'
39
43
  require 'jira/resource/agile'
40
44
  require 'jira/resource/board'
45
+ require 'jira/resource/board_configuration'
41
46
 
42
47
  require 'jira/request_client'
43
48
  require 'jira/oauth_client'
44
49
  require 'jira/http_client'
50
+ require 'jira/jwt_client'
45
51
  require 'jira/client'
46
52
 
47
53
  require 'jira/railtie' if defined?(Rails)
@@ -21,18 +21,18 @@ describe JIRA::Resource::User do
21
21
  describe '#all' do
22
22
  let(:client) do
23
23
  client = double(options: { rest_base_path: '/jira/rest/api/2' })
24
- allow(client).to receive(:get).with('/rest/api/2/user/search?username=_&maxResults=1000').and_return(JIRA::Resource::UserFactory.new(client))
24
+ allow(client).to receive(:get).with('/rest/api/2/users/search?username=_&maxResults=1000').and_return(JIRA::Resource::UserFactory.new(client))
25
25
  client
26
26
  end
27
27
 
28
28
  before do
29
29
  allow(client).to receive(:get)
30
- .with('/rest/api/2/user/search?username=_&maxResults=1000') { OpenStruct.new(body: '["User1"]') }
30
+ .with('/rest/api/2/users/search?username=_&maxResults=1000') { OpenStruct.new(body: '["User1"]') }
31
31
  allow(client).to receive_message_chain(:User, :build).with('users') { [] }
32
32
  end
33
33
 
34
34
  it 'gets users with maxResults of 1000' do
35
- expect(client).to receive(:get).with('/rest/api/2/user/search?username=_&maxResults=1000')
35
+ expect(client).to receive(:get).with('/rest/api/2/users/search?username=_&maxResults=1000')
36
36
  expect(client).to receive_message_chain(:User, :build).with('User1')
37
37
  JIRA::Resource::User.all(client)
38
38
  end
@@ -33,21 +33,30 @@ describe JIRA::Resource::Watcher do
33
33
  end
34
34
 
35
35
  describe 'watchers' do
36
- it 'should returns all the watchers' do
37
- stub_request(:get,
38
- site_url + '/jira/rest/api/2/issue/10002')
36
+ before(:each) do
37
+ stub_request(:get, site_url + '/jira/rest/api/2/issue/10002')
39
38
  .to_return(status: 200, body: get_mock_response('issue/10002.json'))
40
39
 
41
- stub_request(:get,
42
- site_url + '/jira/rest/api/2/issue/10002/watchers')
40
+ stub_request(:get, site_url + '/jira/rest/api/2/issue/10002/watchers')
43
41
  .to_return(status: 200, body: get_mock_response('issue/10002/watchers.json'))
44
42
 
43
+ stub_request(:post, site_url + '/jira/rest/api/2/issue/10002/watchers')
44
+ .to_return(status: 204, body: nil)
45
+ end
46
+
47
+ it 'should returns all the watchers' do
45
48
  issue = client.Issue.find('10002')
46
49
  watchers = client.Watcher.all(options = { issue: issue })
47
50
  expect(watchers.length).to eq(1)
48
51
  end
52
+
53
+ it 'should add a watcher' do
54
+ issue = client.Issue.find('10002')
55
+ watcher = JIRA::Resource::Watcher.new(client, issue: issue)
56
+ user_id = "tester"
57
+ watcher.save!(user_id)
58
+ end
49
59
  end
50
60
 
51
- it_should_behave_like 'a resource'
52
61
  end
53
62
  end
@@ -369,6 +369,18 @@ describe JIRA::Base do
369
369
  expect(subject.url).to eq('http://foo/bar')
370
370
  end
371
371
 
372
+ it 'returns path as the URL if set and site options is specified' do
373
+ allow(client).to receive(:options) { { site: 'http://foo' } }
374
+ attrs['self'] = 'http://foo/bar'
375
+ expect(subject.url).to eq('/bar')
376
+ end
377
+
378
+ it 'returns path as the URL if set and site options is specified and ends with a slash' do
379
+ allow(client).to receive(:options) { { site: 'http://foo/' } }
380
+ attrs['self'] = 'http://foo/bar'
381
+ expect(subject.url).to eq('/bar')
382
+ end
383
+
372
384
  it 'generates the URL from id if self not set' do
373
385
  attrs['self'] = nil
374
386
  attrs['id'] = '98765'
@@ -59,6 +59,19 @@ RSpec.shared_examples 'Client Common Tests' do
59
59
  expect(subject.Project.find('123')).to eq(find_result)
60
60
  end
61
61
  end
62
+
63
+ describe 'SSL client options' do
64
+ context 'without certificate and key' do
65
+ let(:options) { { use_client_cert: true } }
66
+ subject { JIRA::Client.new(options) }
67
+
68
+ it 'raises an ArgumentError' do
69
+ expect { subject }.to raise_exception(ArgumentError, 'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true')
70
+ options[:ssl_client_cert] = '<cert></cert>'
71
+ expect { subject }.to raise_exception(ArgumentError, 'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true')
72
+ end
73
+ end
74
+ end
62
75
  end
63
76
 
64
77
  RSpec.shared_examples 'HttpClient tests' do
@@ -207,6 +220,54 @@ describe JIRA::Client do
207
220
  end
208
221
  end
209
222
 
223
+ context 'with jwt authentication' do
224
+ subject do
225
+ JIRA::Client.new(
226
+ issuer: 'foo',
227
+ base_url: 'https://host.tld',
228
+ shared_secret: 'shared_secret_key',
229
+ auth_type: :jwt
230
+ )
231
+ end
232
+
233
+ before(:each) do
234
+ stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
235
+ .with(query: hash_including(:jwt))
236
+ .to_return(status: 200, body: '[]', headers: {})
237
+ end
238
+
239
+ include_examples 'Client Common Tests'
240
+ include_examples 'HttpClient tests'
241
+
242
+ specify { expect(subject.request_client).to be_a JIRA::JwtClient }
243
+
244
+ it 'sets the username and password' do
245
+ expect(subject.options[:shared_secret]).to eq('shared_secret_key')
246
+ end
247
+
248
+ context 'with a incorrect jwt key' do
249
+ before do
250
+ stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
251
+ .with(query: hash_including(:jwt))
252
+ .to_return(status: 401, body: '[]', headers: {})
253
+ end
254
+
255
+ it 'is not authenticated' do
256
+ expect(subject.authenticated?).to be_falsey
257
+ end
258
+
259
+ it 'raises a JIRA::HTTPError when trying to fetch projects' do
260
+ expect { subject.Project.all }.to raise_error JIRA::HTTPError
261
+ end
262
+ end
263
+
264
+ it 'only returns a true for #authenticated? once we have requested some data' do
265
+ expect(subject.authenticated?).to be_falsey
266
+ expect(subject.Project.all).to be_empty
267
+ expect(subject.authenticated?).to be_truthy
268
+ end
269
+ end
270
+
210
271
  context 'oauth authentication' do
211
272
  subject { JIRA::Client.new(consumer_key: 'foo', consumer_secret: 'bar') }
212
273
 
@@ -218,4 +279,13 @@ describe JIRA::Client do
218
279
 
219
280
  include_examples 'OAuth Common Tests'
220
281
  end
282
+
283
+ context 'with unknown options' do
284
+ let(:options) { { 'username' => 'foo', 'password' => 'bar', auth_type: :basic } }
285
+ subject { JIRA::Client.new(options) }
286
+
287
+ it 'raises an ArgumentError' do
288
+ expect { subject }.to raise_exception(ArgumentError, 'Unknown option(s) given: ["username", "password"]')
289
+ end
290
+ end
221
291
  end
@@ -2,12 +2,22 @@ require 'spec_helper'
2
2
 
3
3
  describe JIRA::HttpClient do
4
4
  let(:basic_client) do
5
- options = JIRA::Client::DEFAULT_OPTIONS.merge(JIRA::HttpClient::DEFAULT_OPTIONS)
5
+ options = JIRA::Client::DEFAULT_OPTIONS
6
+ .merge(JIRA::HttpClient::DEFAULT_OPTIONS)
7
+ .merge(basic_auth_credentials)
6
8
  JIRA::HttpClient.new(options)
7
9
  end
8
10
 
9
11
  let(:basic_cookie_client) do
10
- options = JIRA::Client::DEFAULT_OPTIONS.merge(JIRA::HttpClient::DEFAULT_OPTIONS).merge(use_cookies: true)
12
+ options = JIRA::Client::DEFAULT_OPTIONS
13
+ .merge(JIRA::HttpClient::DEFAULT_OPTIONS)
14
+ .merge(use_cookies: true)
15
+ .merge(basic_auth_credentials)
16
+ JIRA::HttpClient.new(options)
17
+ end
18
+
19
+ let(:custom_ssl_version_client) do
20
+ options = JIRA::Client::DEFAULT_OPTIONS.merge(JIRA::HttpClient::DEFAULT_OPTIONS).merge(ssl_version: :TLSv1_2)
11
21
  JIRA::HttpClient.new(options)
12
22
  end
13
23
 
@@ -20,10 +30,13 @@ describe JIRA::HttpClient do
20
30
  end
21
31
 
22
32
  let(:basic_cookie_client_with_additional_cookies) do
23
- options = JIRA::Client::DEFAULT_OPTIONS.merge(JIRA::HttpClient::DEFAULT_OPTIONS).merge(
24
- use_cookies: true,
25
- additional_cookies: ['sessionToken=abc123', 'internal=true']
26
- )
33
+ options = JIRA::Client::DEFAULT_OPTIONS
34
+ .merge(JIRA::HttpClient::DEFAULT_OPTIONS)
35
+ .merge(
36
+ use_cookies: true,
37
+ additional_cookies: ['sessionToken=abc123', 'internal=true']
38
+ )
39
+ .merge(basic_auth_credentials)
27
40
  JIRA::HttpClient.new(options)
28
41
  end
29
42
 
@@ -36,6 +49,26 @@ describe JIRA::HttpClient do
36
49
  JIRA::HttpClient.new(options)
37
50
  end
38
51
 
52
+ let(:basic_client_with_no_auth_credentials) do
53
+ options = JIRA::Client::DEFAULT_OPTIONS
54
+ .merge(JIRA::HttpClient::DEFAULT_OPTIONS)
55
+ JIRA::HttpClient.new(options)
56
+ end
57
+
58
+ let(:basic_auth_credentials) do
59
+ { username: 'donaldduck', password: 'supersecret' }
60
+ end
61
+
62
+ let(:proxy_client) do
63
+ options = JIRA::Client::DEFAULT_OPTIONS.merge(JIRA::HttpClient::DEFAULT_OPTIONS).merge(
64
+ proxy_address: 'proxyAddress',
65
+ proxy_port: 42,
66
+ proxy_username: 'proxyUsername',
67
+ proxy_password: 'proxyPassword'
68
+ )
69
+ JIRA::HttpClient.new(options)
70
+ end
71
+
39
72
  let(:response) do
40
73
  response = double('response')
41
74
  allow(response).to receive(:kind_of?).with(Net::HTTPSuccess).and_return(true)
@@ -159,6 +192,19 @@ describe JIRA::HttpClient do
159
192
  basic_client.make_request(:get, 'http://mydomain.com/foo', body, headers)
160
193
  end
161
194
 
195
+ it 'does not try to use basic auth if the credentials are not set' do
196
+ body = nil
197
+ headers = double
198
+ basic_auth_http_conn = double
199
+ http_request = double
200
+ expect(Net::HTTP::Get).to receive(:new).with('/foo', headers).and_return(http_request)
201
+
202
+ expect(basic_auth_http_conn).to receive(:request).with(http_request).and_return(response)
203
+ expect(http_request).not_to receive(:basic_auth)
204
+ allow(basic_client_with_no_auth_credentials).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
205
+ basic_client_with_no_auth_credentials.make_request(:get, '/foo', body, headers)
206
+ end
207
+
162
208
  it 'returns a URI' do
163
209
  uri = URI.parse(basic_client.options[:site])
164
210
  expect(basic_client.uri).to eq(uri)
@@ -178,6 +224,51 @@ describe JIRA::HttpClient do
178
224
  expect(basic_client.http_conn(uri)).to eq(http_conn)
179
225
  end
180
226
 
227
+ it 'sets the SSL version when one is provided' do
228
+ http_conn = double
229
+ uri = double
230
+ host = double
231
+ port = double
232
+ expect(uri).to receive(:host).and_return(host)
233
+ expect(uri).to receive(:port).and_return(port)
234
+ expect(Net::HTTP).to receive(:new).with(host, port).and_return(http_conn)
235
+ expect(http_conn).to receive(:use_ssl=).with(basic_client.options[:use_ssl]).and_return(http_conn)
236
+ expect(http_conn).to receive(:verify_mode=).with(basic_client.options[:ssl_verify_mode]).and_return(http_conn)
237
+ expect(http_conn).to receive(:ssl_version=).with(custom_ssl_version_client.options[:ssl_version]).and_return(http_conn)
238
+ expect(http_conn).to receive(:read_timeout=).with(basic_client.options[:read_timeout]).and_return(http_conn)
239
+ expect(custom_ssl_version_client.http_conn(uri)).to eq(http_conn)
240
+ end
241
+
242
+ it 'sets up a non-proxied http connection by default' do
243
+ uri = double
244
+ host = double
245
+ port = double
246
+
247
+ expect(uri).to receive(:host).and_return(host)
248
+ expect(uri).to receive(:port).and_return(port)
249
+
250
+ proxy_configuration = basic_client.http_conn(uri).class
251
+ expect(proxy_configuration.proxy_address).to be_nil
252
+ expect(proxy_configuration.proxy_port).to be_nil
253
+ expect(proxy_configuration.proxy_user).to be_nil
254
+ expect(proxy_configuration.proxy_pass).to be_nil
255
+ end
256
+
257
+ it 'sets up a proxied http connection when using proxy options' do
258
+ uri = double
259
+ host = double
260
+ port = double
261
+
262
+ expect(uri).to receive(:host).and_return(host)
263
+ expect(uri).to receive(:port).and_return(port)
264
+
265
+ proxy_configuration = proxy_client.http_conn(uri).class
266
+ expect(proxy_configuration.proxy_address).to eq(proxy_client.options[:proxy_address])
267
+ expect(proxy_configuration.proxy_port).to eq(proxy_client.options[:proxy_port])
268
+ expect(proxy_configuration.proxy_user).to eq(proxy_client.options[:proxy_username])
269
+ expect(proxy_configuration.proxy_pass).to eq(proxy_client.options[:proxy_password])
270
+ end
271
+
181
272
  it 'can use client certificates' do
182
273
  http_conn = double
183
274
  uri = double
@@ -189,11 +280,16 @@ describe JIRA::HttpClient do
189
280
  expect(http_conn).to receive(:use_ssl=).with(basic_client.options[:use_ssl])
190
281
  expect(http_conn).to receive(:verify_mode=).with(basic_client.options[:ssl_verify_mode])
191
282
  expect(http_conn).to receive(:read_timeout=).with(basic_client.options[:read_timeout])
192
- expect(http_conn).to receive(:cert=).with(basic_client_cert_client.options[:cert])
193
- expect(http_conn).to receive(:key=).with(basic_client_cert_client.options[:key])
283
+ expect(http_conn).to receive(:cert=).with(basic_client_cert_client.options[:ssl_client_cert])
284
+ expect(http_conn).to receive(:key=).with(basic_client_cert_client.options[:ssl_client_key])
194
285
  expect(basic_client_cert_client.http_conn(uri)).to eq(http_conn)
195
286
  end
196
287
 
288
+ it 'can use a certificate authority file' do
289
+ client = JIRA::HttpClient.new(JIRA::Client::DEFAULT_OPTIONS.merge(ca_file: '/opt/custom.ca.pem'))
290
+ expect(client.http_conn(client.uri).ca_file).to eql('/opt/custom.ca.pem')
291
+ end
292
+
197
293
  it 'returns a http connection' do
198
294
  http_conn = double
199
295
  uri = double
@@ -201,4 +297,37 @@ describe JIRA::HttpClient do
201
297
  expect(basic_client).to receive(:http_conn).and_return(http_conn)
202
298
  expect(basic_client.basic_auth_http_conn).to eq(http_conn)
203
299
  end
300
+
301
+ describe '#make_multipart_request' do
302
+ subject do
303
+ basic_client.make_multipart_request(path, data, headers)
304
+ end
305
+
306
+ let(:path) { '/foo' }
307
+ let(:data) { {} }
308
+ let(:headers) { { 'X-Atlassian-Token' => 'no-check' } }
309
+ let(:basic_auth_http_conn) { double }
310
+ let(:request) { double('Http Request', path: path) }
311
+ let(:response) { double('response') }
312
+
313
+ before do
314
+ allow(request).to receive(:basic_auth)
315
+ allow(Net::HTTP::Post::Multipart).to receive(:new).with(path, data, headers).and_return(request)
316
+ allow(basic_client).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
317
+ allow(basic_auth_http_conn).to receive(:request).with(request).and_return(response)
318
+ end
319
+
320
+ it 'performs a basic http client request' do
321
+ expect(request).to receive(:basic_auth).with(basic_client.options[:username], basic_client.options[:password]).and_return(request)
322
+
323
+ subject
324
+ end
325
+
326
+ it 'makes a correct HTTP request' do
327
+ expect(basic_auth_http_conn).to receive(:request).with(request).and_return(response)
328
+ expect(response).to receive(:is_a?).with(Net::HTTPOK)
329
+
330
+ subject
331
+ end
332
+ end
204
333
  end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::JwtClient::JwtUriBuilder do
4
+ subject(:url_builder) do
5
+ JIRA::JwtClient::JwtUriBuilder.new(url, http_method, shared_secret, site, issuer)
6
+ end
7
+
8
+ let(:url) { '/foo' }
9
+ let(:http_method) { :get }
10
+ let(:shared_secret) { 'shared_secret' }
11
+ let(:site) { 'http://localhost:2990' }
12
+ let(:issuer) { nil }
13
+
14
+ describe '#build' do
15
+ subject { url_builder.build }
16
+
17
+ it 'includes the jwt param' do
18
+ expect(subject).to include('?jwt=')
19
+ end
20
+
21
+ context 'when the url already contains params' do
22
+ let(:url) { '/foo?expand=projects.issuetypes.fields' }
23
+
24
+ it 'includes the jwt param' do
25
+ expect(subject).to include('&jwt=')
26
+ end
27
+ end
28
+
29
+ context 'with a complete url' do
30
+ let(:url) { 'http://localhost:2990/rest/api/2/issue/createmeta' }
31
+
32
+ it 'includes the jwt param' do
33
+ expect(subject).to include('?jwt=')
34
+ end
35
+
36
+ it { is_expected.to start_with('/') }
37
+
38
+ it 'contains only one ?' do
39
+ expect(subject.count('?')).to eq(1)
40
+ end
41
+ end
42
+
43
+ context 'with a complete url containing a param' do
44
+ let(:url) do
45
+ 'http://localhost:2990/rest/api/2/issue/createmeta?expand=projects.issuetypes.fields'
46
+ end
47
+
48
+ it 'includes the jwt param' do
49
+ expect(subject).to include('&jwt=')
50
+ end
51
+
52
+ it { is_expected.to start_with('/') }
53
+
54
+ it 'contains only one ?' do
55
+ expect(subject.count('?')).to eq(1)
56
+ end
57
+ end
58
+ end
59
+ end