jira-ruby 1.6.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,6 @@
1
1
  require 'oauth'
2
2
  require 'json'
3
3
  require 'net/https'
4
- # require 'pry'
5
4
 
6
5
  module JIRA
7
6
  class RequestClient
@@ -11,9 +10,22 @@ module JIRA
11
10
 
12
11
  def request(*args)
13
12
  response = make_request(*args)
14
- # binding.pry unless response.kind_of?(Net::HTTPSuccess)
15
13
  raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
16
14
  response
17
15
  end
16
+
17
+ def request_multipart(*args)
18
+ response = make_multipart_request(*args)
19
+ raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
20
+ response
21
+ end
22
+
23
+ def make_request(*args)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def make_multipart_request(*args)
28
+ raise NotImplementedError
29
+ end
18
30
  end
19
- end
31
+ end
@@ -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
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module JIRA
2
- VERSION = '1.6.0'.freeze
2
+ VERSION = '2.1.0'.freeze
3
3
  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'
@@ -207,6 +207,54 @@ describe JIRA::Client do
207
207
  end
208
208
  end
209
209
 
210
+ context 'with jwt authentication' do
211
+ subject do
212
+ JIRA::Client.new(
213
+ issuer: 'foo',
214
+ base_url: 'https://host.tld',
215
+ shared_secret: 'shared_secret_key',
216
+ auth_type: :jwt
217
+ )
218
+ end
219
+
220
+ before(:each) do
221
+ stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
222
+ .with(query: hash_including(:jwt))
223
+ .to_return(status: 200, body: '[]', headers: {})
224
+ end
225
+
226
+ include_examples 'Client Common Tests'
227
+ include_examples 'HttpClient tests'
228
+
229
+ specify { expect(subject.request_client).to be_a JIRA::JwtClient }
230
+
231
+ it 'sets the username and password' do
232
+ expect(subject.options[:shared_secret]).to eq('shared_secret_key')
233
+ end
234
+
235
+ context 'with a incorrect jwt key' do
236
+ before do
237
+ stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
238
+ .with(query: hash_including(:jwt))
239
+ .to_return(status: 401, body: '[]', headers: {})
240
+ end
241
+
242
+ it 'is not authenticated' do
243
+ expect(subject.authenticated?).to be_falsey
244
+ end
245
+
246
+ it 'raises a JIRA::HTTPError when trying to fetch projects' do
247
+ expect { subject.Project.all }.to raise_error JIRA::HTTPError
248
+ end
249
+ end
250
+
251
+ it 'only returns a true for #authenticated? once we have requested some data' do
252
+ expect(subject.authenticated?).to be_falsey
253
+ expect(subject.Project.all).to be_empty
254
+ expect(subject.authenticated?).to be_truthy
255
+ end
256
+ end
257
+
210
258
  context 'oauth authentication' do
211
259
  subject { JIRA::Client.new(consumer_key: 'foo', consumer_secret: 'bar') }
212
260
 
@@ -218,4 +266,13 @@ describe JIRA::Client do
218
266
 
219
267
  include_examples 'OAuth Common Tests'
220
268
  end
269
+
270
+ context 'with unknown options' do
271
+ let(:options) { { 'username' => 'foo', 'password' => 'bar', auth_type: :basic } }
272
+ subject { JIRA::Client.new(options) }
273
+
274
+ it 'raises an ArgumentError' do
275
+ expect { subject }.to raise_exception(ArgumentError, 'Unknown option(s) given: ["username", "password"]')
276
+ end
277
+ end
221
278
  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
@@ -201,4 +292,37 @@ describe JIRA::HttpClient do
201
292
  expect(basic_client).to receive(:http_conn).and_return(http_conn)
202
293
  expect(basic_client.basic_auth_http_conn).to eq(http_conn)
203
294
  end
295
+
296
+ describe '#make_multipart_request' do
297
+ subject do
298
+ basic_client.make_multipart_request(path, data, headers)
299
+ end
300
+
301
+ let(:path) { '/foo' }
302
+ let(:data) { {} }
303
+ let(:headers) { { 'X-Atlassian-Token' => 'no-check' } }
304
+ let(:basic_auth_http_conn) { double }
305
+ let(:request) { double('Http Request', path: path) }
306
+ let(:response) { double('response') }
307
+
308
+ before do
309
+ allow(request).to receive(:basic_auth)
310
+ allow(Net::HTTP::Post::Multipart).to receive(:new).with(path, data, headers).and_return(request)
311
+ allow(basic_client).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
312
+ allow(basic_auth_http_conn).to receive(:request).with(request).and_return(response)
313
+ end
314
+
315
+ it 'performs a basic http client request' do
316
+ expect(request).to receive(:basic_auth).with(basic_client.options[:username], basic_client.options[:password]).and_return(request)
317
+
318
+ subject
319
+ end
320
+
321
+ it 'makes a correct HTTP request' do
322
+ expect(basic_auth_http_conn).to receive(:request).with(request).and_return(response)
323
+ expect(response).to receive(:is_a?).with(Net::HTTPOK)
324
+
325
+ subject
326
+ end
327
+ end
204
328
  end