jira-ruby 1.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.travis.yml +5 -3
  4. data/Gemfile +7 -1
  5. data/Guardfile +1 -1
  6. data/README.md +452 -0
  7. data/Rakefile +6 -7
  8. data/example.rb +23 -1
  9. data/http-basic-example.rb +13 -12
  10. data/jira-ruby.gemspec +13 -13
  11. data/lib/jira/base.rb +53 -52
  12. data/lib/jira/base_factory.rb +3 -6
  13. data/lib/jira/client.rb +127 -30
  14. data/lib/jira/has_many_proxy.rb +0 -1
  15. data/lib/jira/http_client.rb +54 -16
  16. data/lib/jira/http_error.rb +3 -5
  17. data/lib/jira/jwt_client.rb +67 -0
  18. data/lib/jira/oauth_client.rb +47 -17
  19. data/lib/jira/request_client.rb +16 -5
  20. data/lib/jira/resource/agile.rb +34 -9
  21. data/lib/jira/resource/applinks.rb +5 -8
  22. data/lib/jira/resource/attachment.rb +41 -3
  23. data/lib/jira/resource/board.rb +91 -0
  24. data/lib/jira/resource/board_configuration.rb +9 -0
  25. data/lib/jira/resource/comment.rb +0 -2
  26. data/lib/jira/resource/component.rb +1 -3
  27. data/lib/jira/resource/createmeta.rb +12 -14
  28. data/lib/jira/resource/field.rb +22 -22
  29. data/lib/jira/resource/filter.rb +2 -2
  30. data/lib/jira/resource/issue.rb +69 -38
  31. data/lib/jira/resource/issue_picker_suggestions.rb +24 -0
  32. data/lib/jira/resource/issue_picker_suggestions_issue.rb +10 -0
  33. data/lib/jira/resource/issuelink.rb +3 -5
  34. data/lib/jira/resource/issuelinktype.rb +0 -1
  35. data/lib/jira/resource/issuetype.rb +1 -3
  36. data/lib/jira/resource/priority.rb +1 -3
  37. data/lib/jira/resource/project.rb +5 -7
  38. data/lib/jira/resource/rapidview.rb +28 -7
  39. data/lib/jira/resource/remotelink.rb +1 -4
  40. data/lib/jira/resource/resolution.rb +2 -4
  41. data/lib/jira/resource/serverinfo.rb +1 -2
  42. data/lib/jira/resource/sprint.rb +86 -17
  43. data/lib/jira/resource/sprint_report.rb +8 -0
  44. data/lib/jira/resource/status.rb +1 -3
  45. data/lib/jira/resource/suggested_issue.rb +9 -0
  46. data/lib/jira/resource/transition.rb +2 -6
  47. data/lib/jira/resource/user.rb +12 -2
  48. data/lib/jira/resource/version.rb +1 -3
  49. data/lib/jira/resource/watcher.rb +35 -0
  50. data/lib/jira/resource/webhook.rb +3 -6
  51. data/lib/jira/resource/worklog.rb +3 -5
  52. data/lib/jira/version.rb +1 -1
  53. data/lib/jira-ruby.rb +12 -2
  54. data/lib/tasks/generate.rake +4 -4
  55. data/spec/integration/attachment_spec.rb +17 -8
  56. data/spec/integration/comment_spec.rb +31 -34
  57. data/spec/integration/component_spec.rb +21 -24
  58. data/spec/integration/field_spec.rb +15 -18
  59. data/spec/integration/issue_spec.rb +45 -46
  60. data/spec/integration/issuelinktype_spec.rb +8 -11
  61. data/spec/integration/issuetype_spec.rb +5 -7
  62. data/spec/integration/priority_spec.rb +5 -8
  63. data/spec/integration/project_spec.rb +13 -20
  64. data/spec/integration/rapidview_spec.rb +17 -10
  65. data/spec/integration/resolution_spec.rb +7 -10
  66. data/spec/integration/status_spec.rb +5 -8
  67. data/spec/integration/transition_spec.rb +17 -20
  68. data/spec/integration/user_spec.rb +24 -8
  69. data/spec/integration/version_spec.rb +21 -25
  70. data/spec/integration/watcher_spec.rb +62 -0
  71. data/spec/integration/webhook.rb +8 -17
  72. data/spec/integration/worklog_spec.rb +30 -34
  73. data/spec/jira/base_factory_spec.rb +11 -12
  74. data/spec/jira/base_spec.rb +216 -229
  75. data/spec/jira/client_spec.rb +227 -159
  76. data/spec/jira/has_many_proxy_spec.rb +11 -12
  77. data/spec/jira/http_client_spec.rb +254 -31
  78. data/spec/jira/http_error_spec.rb +7 -9
  79. data/spec/jira/jwt_uri_builder_spec.rb +59 -0
  80. data/spec/jira/oauth_client_spec.rb +110 -39
  81. data/spec/jira/request_client_spec.rb +36 -9
  82. data/spec/jira/resource/agile_spec.rb +135 -0
  83. data/spec/jira/resource/attachment_spec.rb +127 -9
  84. data/spec/jira/resource/board_spec.rb +224 -0
  85. data/spec/jira/resource/createmeta_spec.rb +29 -32
  86. data/spec/jira/resource/field_spec.rb +42 -48
  87. data/spec/jira/resource/filter_spec.rb +40 -40
  88. data/spec/jira/resource/issue_picker_suggestions_spec.rb +79 -0
  89. data/spec/jira/resource/issue_spec.rb +88 -85
  90. data/spec/jira/resource/issuelink_spec.rb +1 -1
  91. data/spec/jira/resource/jira_picker_suggestions_issue_spec.rb +18 -0
  92. data/spec/jira/resource/project_factory_spec.rb +2 -4
  93. data/spec/jira/resource/project_spec.rb +33 -33
  94. data/spec/jira/resource/sprint_spec.rb +90 -0
  95. data/spec/jira/resource/user_factory_spec.rb +6 -8
  96. data/spec/jira/resource/worklog_spec.rb +9 -11
  97. data/spec/mock_responses/board/1.json +33 -0
  98. data/spec/mock_responses/board/1_issues.json +62 -0
  99. data/spec/mock_responses/empty_issues.json +8 -0
  100. data/spec/mock_responses/issue/10002/watchers.json +13 -0
  101. data/spec/mock_responses/issue.json +1 -1
  102. data/spec/mock_responses/sprint/1_issues.json +125 -0
  103. data/spec/spec_helper.rb +8 -9
  104. data/spec/support/clients_helper.rb +4 -4
  105. data/spec/support/shared_examples/integration.rb +60 -77
  106. metadata +115 -55
  107. data/.ruby-version +0 -1
  108. data/README.rdoc +0 -333
  109. /data/spec/mock_responses/{attachment → issue/10002/attachments}/10000.json +0 -0
data/lib/jira/base.rb CHANGED
@@ -3,7 +3,6 @@ require 'active_support/inflector'
3
3
  require 'set'
4
4
 
5
5
  module JIRA
6
-
7
6
  # This class provides the basic object <-> REST mapping for all JIRA::Resource subclasses,
8
7
  # i.e. the Create, Retrieve, Update, Delete lifecycle methods.
9
8
  #
@@ -51,8 +50,8 @@ module JIRA
51
50
  # new_comment = issue.comments.build
52
51
  #
53
52
  class Base
54
- QUERY_PARAMS_FOR_SINGLE_FETCH = Set.new [:expand, :fields]
55
- QUERY_PARAMS_FOR_SEARCH = Set.new [:expand, :fields, :startAt, :maxResults]
53
+ QUERY_PARAMS_FOR_SINGLE_FETCH = Set.new %i[expand fields]
54
+ QUERY_PARAMS_FOR_SEARCH = Set.new %i[expand fields startAt maxResults]
56
55
 
57
56
  # A reference to the JIRA::Client used to initialize this resource.
58
57
  attr_reader :client
@@ -67,8 +66,8 @@ module JIRA
67
66
  # representation of the JSON returned from the JIRA API
68
67
  attr_accessor :attrs
69
68
 
70
- alias :expanded? :expanded
71
- alias :deleted? :deleted
69
+ alias expanded? expanded
70
+ alias deleted? deleted
72
71
 
73
72
  def initialize(client, options = {})
74
73
  @client = client
@@ -80,12 +79,12 @@ module JIRA
80
79
  # each of them must be passed in to the initializer.
81
80
  self.class.belongs_to_relationships.each do |relation|
82
81
  if options[relation]
83
- instance_variable_set("@#{relation.to_s}", options[relation])
84
- instance_variable_set("@#{relation.to_s}_id", options[relation].key_value)
82
+ instance_variable_set("@#{relation}", options[relation])
83
+ instance_variable_set("@#{relation}_id", options[relation].key_value)
85
84
  elsif options["#{relation}_id".to_sym]
86
- instance_variable_set("@#{relation.to_s}_id", options["#{relation}_id".to_sym])
85
+ instance_variable_set("@#{relation}_id", options["#{relation}_id".to_sym])
87
86
  else
88
- raise ArgumentError.new("Required option #{relation.inspect} missing") unless options[relation]
87
+ raise ArgumentError, "Required option #{relation.inspect} missing" unless options[relation]
89
88
  end
90
89
  end
91
90
  end
@@ -95,17 +94,15 @@ module JIRA
95
94
  def self.all(client, options = {})
96
95
  response = client.get(collection_path(client))
97
96
  json = parse_json(response.body)
98
- if collection_attributes_are_nested
99
- json = json[endpoint_name.pluralize]
100
- end
97
+ json = json[endpoint_name.pluralize] if collection_attributes_are_nested
101
98
  json.map do |attrs|
102
- self.new(client, {:attrs => attrs}.merge(options))
99
+ new(client, { attrs: attrs }.merge(options))
103
100
  end
104
101
  end
105
102
 
106
103
  # Finds and retrieves a resource with the given ID.
107
104
  def self.find(client, key, options = {})
108
- instance = self.new(client, options)
105
+ instance = new(client, options)
109
106
  instance.attrs[key_attribute.to_s] = key
110
107
  instance.fetch(false, query_params_for_single_fetch(options))
111
108
  instance
@@ -114,7 +111,7 @@ module JIRA
114
111
  # Builds a new instance of the resource with the given attributes.
115
112
  # These attributes will be posted to the JIRA Api if save is called.
116
113
  def self.build(client, attrs)
117
- self.new(client, :attrs => attrs)
114
+ new(client, attrs: attrs)
118
115
  end
119
116
 
120
117
  # Returns the name of this resource for use in URL components.
@@ -122,7 +119,7 @@ module JIRA
122
119
  # JIRA::Resource::Issue.endpoint_name
123
120
  # # => issue
124
121
  def self.endpoint_name
125
- self.name.split('::').last.downcase
122
+ name.split('::').last.downcase
126
123
  end
127
124
 
128
125
  # Returns the full path for a collection of this resource.
@@ -130,7 +127,7 @@ module JIRA
130
127
  # JIRA::Resource::Issue.collection_path
131
128
  # # => /jira/rest/api/2/issue
132
129
  def self.collection_path(client, prefix = '/')
133
- client.options[:rest_base_path] + prefix + self.endpoint_name
130
+ client.options[:rest_base_path] + prefix + endpoint_name
134
131
  end
135
132
 
136
133
  # Returns the singular path for the resource with the given key.
@@ -200,7 +197,7 @@ module JIRA
200
197
  define_method(resource) do
201
198
  attribute = maybe_nested_attribute(attribute_key, options[:nested_under])
202
199
  return nil unless attribute
203
- child_class.new(client, :attrs => attribute)
200
+ child_class.new(client, attrs: attribute)
204
201
  end
205
202
  end
206
203
 
@@ -248,12 +245,12 @@ module JIRA
248
245
  def self.has_many(collection, options = {})
249
246
  attribute_key = options[:attribute_key] || collection.to_s
250
247
  child_class = options[:class] || ('JIRA::Resource::' + collection.to_s.classify).constantize
251
- self_class_basename = self.name.split('::').last.downcase.to_sym
248
+ self_class_basename = name.split('::').last.downcase.to_sym
252
249
  define_method(collection) do
253
- child_class_options = {self_class_basename => self}
250
+ child_class_options = { self_class_basename => self }
254
251
  attribute = maybe_nested_attribute(attribute_key, options[:nested_under]) || []
255
252
  collection = attribute.map do |child_attributes|
256
- child_class.new(client, child_class_options.merge(:attrs => child_attributes))
253
+ child_class.new(client, child_class_options.merge(attrs: child_attributes))
257
254
  end
258
255
  HasManyProxy.new(self, child_class, collection)
259
256
  end
@@ -290,8 +287,8 @@ module JIRA
290
287
  # Checks if method_name is set in the attributes hash
291
288
  # and returns true when found, otherwise proxies the
292
289
  # call to the superclass.
293
- def respond_to?(method_name, include_all=false)
294
- if attrs.keys.include? method_name.to_s
290
+ def respond_to?(method_name, _include_all = false)
291
+ if attrs.key?(method_name.to_s)
295
292
  true
296
293
  else
297
294
  super(method_name)
@@ -301,8 +298,8 @@ module JIRA
301
298
  # Overrides method_missing to check the attribute hash
302
299
  # for resources matching method_name and proxies the call
303
300
  # to the superclass if no match is found.
304
- def method_missing(method_name, *args, &block)
305
- if attrs.keys.include? method_name.to_s
301
+ def method_missing(method_name, *_args)
302
+ if attrs.key?(method_name.to_s)
306
303
  attrs[method_name.to_s]
307
304
  else
308
305
  super(method_name)
@@ -315,7 +312,7 @@ module JIRA
315
312
  @attrs[self.class.key_attribute.to_s]
316
313
  end
317
314
 
318
- def collection_path(prefix = "/")
315
+ def collection_path(prefix = '/')
319
316
  # Just proxy this to the class method
320
317
  self.class.collection_path(client, prefix)
321
318
  end
@@ -325,9 +322,7 @@ module JIRA
325
322
  # issue it returns '/issue'
326
323
  def path_component
327
324
  path_component = "/#{self.class.endpoint_name}"
328
- if key_value
329
- path_component += '/' + key_value
330
- end
325
+ path_component += '/' + key_value if key_value
331
326
  path_component
332
327
  end
333
328
 
@@ -346,9 +341,10 @@ module JIRA
346
341
  #
347
342
  # Accepts an attributes hash of the values to be saved. Will throw a
348
343
  # JIRA::HTTPError if the request fails (response is not HTTP 2xx).
349
- def save!(attrs)
344
+ def save!(attrs, path = nil)
345
+ path ||= new_record? ? url : patched_url
350
346
  http_method = new_record? ? :post : :put
351
- response = client.send(http_method, new_record? ? url : patched_url, attrs.to_json)
347
+ response = client.send(http_method, path, attrs.to_json)
352
348
  set_attrs(attrs, false)
353
349
  set_attrs_from_response(response)
354
350
  @expanded = false
@@ -360,21 +356,20 @@ module JIRA
360
356
  #
361
357
  # Accepts an attributes hash of the values to be saved. Will return false
362
358
  # if the request fails.
363
- def save(attrs)
359
+ def save(attrs, path = url)
364
360
  begin
365
- save_status = save!(attrs)
361
+ save_status = save!(attrs, path)
366
362
  rescue JIRA::HTTPError => exception
367
- puts ">>>>>>>>> Exception response: #{exception.response.body}"
368
363
  begin
369
364
  set_attrs_from_response(exception.response) # Merge error status generated by JIRA REST API
370
365
  rescue JSON::ParserError => parse_exception
371
- set_attrs("exception" => {
372
- "class" => exception.response.class.name,
373
- "code" => exception.response.code,
374
- "message" => exception.response.message
375
- }
376
- )
366
+ set_attrs('exception' => {
367
+ 'class' => exception.response.class.name,
368
+ 'code' => exception.response.code,
369
+ 'message' => exception.response.message
370
+ })
377
371
  end
372
+ # raise exception
378
373
  save_status = false
379
374
  end
380
375
  save_status
@@ -383,7 +378,7 @@ module JIRA
383
378
  # Sets the attributes hash from a HTTPResponse object from JIRA if it is
384
379
  # not nil or is not a json response.
385
380
  def set_attrs_from_response(response)
386
- unless response.body.nil? or response.body.length < 2
381
+ unless response.body.nil? || (response.body.length < 2)
387
382
  json = self.class.parse_json(response.body)
388
383
  set_attrs(json)
389
384
  end
@@ -393,7 +388,7 @@ module JIRA
393
388
  # hash values will be clobbered by the new hash, otherwise the hash will
394
389
  # be deeply merged into attrs. The target paramater is for internal use only
395
390
  # and should not be used.
396
- def set_attrs(hash, clobber=true, target = nil)
391
+ def set_attrs(hash, clobber = true, target = nil)
397
392
  target ||= @attrs
398
393
  if clobber
399
394
  target.merge!(hash)
@@ -424,12 +419,12 @@ module JIRA
424
419
  prefix = '/'
425
420
  unless self.class.belongs_to_relationships.empty?
426
421
  prefix = self.class.belongs_to_relationships.inject(prefix) do |prefix_so_far, relationship|
427
- prefix_so_far.to_s + relationship.to_s + "/" + self.send("#{relationship.to_s}_id").to_s + '/'
422
+ prefix_so_far.to_s + relationship.to_s + '/' + send("#{relationship}_id").to_s + '/'
428
423
  end
429
424
  end
430
425
  if @attrs['self']
431
- the_url = @attrs['self'].sub(@client.options[:site],'')
432
- the_url = "/#{the_url}" if (the_url =~ /^\//).nil?
426
+ the_url = @attrs['self']
427
+ the_url = the_url.sub(@client.options[:site].chomp('/'), '') if @client.options[:site]
433
428
  the_url
434
429
  elsif key_value
435
430
  self.class.singular_path(client, key_value.to_s, prefix)
@@ -447,7 +442,7 @@ module JIRA
447
442
  # [07/Jun/2015:15:17:18 +0400] "PUT /jira/rest/api/2/issue/10111 HTTP/1.1" 204 -
448
443
  def patched_url
449
444
  result = url
450
- return result if result.start_with?('/')
445
+ return result if result.start_with?('/', 'http')
451
446
  "/#{result}"
452
447
  end
453
448
 
@@ -494,9 +489,15 @@ module JIRA
494
489
  end
495
490
 
496
491
  def url_with_query_params(url, query_params)
497
- uri = URI.parse(url)
498
- uri.query = uri.query.nil? ? "#{hash_to_query_string query_params}" : "#{uri.query}&#{hash_to_query_string query_params}" unless query_params.empty?
499
- uri.to_s
492
+ self.class.url_with_query_params(url, query_params)
493
+ end
494
+
495
+ def self.url_with_query_params(url, query_params)
496
+ if !query_params.empty?
497
+ "#{url}?#{hash_to_query_string query_params}"
498
+ else
499
+ url
500
+ end
500
501
  end
501
502
 
502
503
  def hash_to_query_string(query_params)
@@ -504,19 +505,19 @@ module JIRA
504
505
  end
505
506
 
506
507
  def self.hash_to_query_string(query_params)
507
- query_params.map do |k,v|
508
+ query_params.map do |k, v|
508
509
  CGI.escape(k.to_s) + '=' + CGI.escape(v.to_s)
509
510
  end.join('&')
510
511
  end
511
512
 
512
513
  def self.query_params_for_single_fetch(options)
513
- Hash[options.select do |k,v|
514
+ Hash[options.select do |k, _v|
514
515
  QUERY_PARAMS_FOR_SINGLE_FETCH.include? k
515
516
  end]
516
517
  end
517
518
 
518
519
  def self.query_params_for_search(options)
519
- Hash[options.select do |k,v|
520
+ Hash[options.select do |k, _v|
520
521
  QUERY_PARAMS_FOR_SEARCH.include? k
521
522
  end]
522
523
  end
@@ -1,15 +1,13 @@
1
1
  module JIRA
2
-
3
2
  # This is the base class for all the JIRA resource factory instances.
4
3
  class BaseFactory
5
-
6
4
  attr_reader :client
7
5
 
8
6
  def initialize(client)
9
7
  @client = client
10
8
  end
11
9
 
12
- # Return the name of the class which this factory generates, i.e.
10
+ # Return the name of the class which this factory generates, i.e.
13
11
  # JIRA::Resource::FooFactory creates JIRA::Resource::Foo instances.
14
12
  def target_class
15
13
  # Need to do a little bit of work here as Module.const_get doesn't work
@@ -38,12 +36,11 @@ module JIRA
38
36
  # The principle purpose of this class is to delegate methods to the corresponding
39
37
  # non-factory class and automatically prepend the client argument to the argument
40
38
  # list.
41
- delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :get_backlog_issues, :get_sprints, :get_sprint_issues
39
+ delegate_to_target_class :all, :find, :collection_path, :singular_path, :jql, :get_backlog_issues, :get_board_issues, :get_sprints, :get_sprint_issues, :get_projects, :get_projects_full
42
40
 
43
41
  # This method needs special handling as it has a default argument value
44
- def build(attrs={})
42
+ def build(attrs = {})
45
43
  target_class.build(@client, attrs)
46
44
  end
47
-
48
45
  end
49
46
  end
data/lib/jira/client.rb CHANGED
@@ -3,11 +3,10 @@ require 'forwardable'
3
3
  require 'ostruct'
4
4
 
5
5
  module JIRA
6
-
7
6
  # This class is the main access point for all JIRA::Resource instances.
8
7
  #
9
8
  # The client must be initialized with an options hash containing
10
- # configuration options. The available options are:
9
+ # configuration options. The available options are:
11
10
  #
12
11
  # :site => 'http://localhost:2990',
13
12
  # :context_path => '/jira',
@@ -15,23 +14,38 @@ module JIRA
15
14
  # :request_token_path => "/plugins/servlet/oauth/request-token",
16
15
  # :authorize_path => "/plugins/servlet/oauth/authorize",
17
16
  # :access_token_path => "/plugins/servlet/oauth/access-token",
17
+ # :private_key => nil,
18
18
  # :private_key_file => "rsakey.pem",
19
19
  # :rest_base_path => "/rest/api/2",
20
20
  # :consumer_key => nil,
21
21
  # :consumer_secret => nil,
22
22
  # :ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER,
23
+ # :ssl_version => nil,
23
24
  # :use_ssl => true,
24
25
  # :username => nil,
25
26
  # :password => nil,
26
- # :auth_type => :oauth
27
- # :proxy_address => nil
28
- # :proxy_port => nil
27
+ # :auth_type => :oauth,
28
+ # :proxy_address => nil,
29
+ # :proxy_port => nil,
30
+ # :proxy_username => nil,
31
+ # :proxy_password => nil,
32
+ # :use_cookies => nil,
33
+ # :additional_cookies => nil,
34
+ # :default_headers => {},
35
+ # :use_client_cert => false,
36
+ # :read_timeout => nil,
37
+ # :http_debug => false,
38
+ # :shared_secret => nil,
39
+ # :cert_path => nil,
40
+ # :key_path => nil,
41
+ # :ssl_client_cert => nil,
42
+ # :ssl_client_key => nil
43
+ # :ca_file => nil
29
44
  #
30
45
  # See the JIRA::Base class methods for all of the available methods on these accessor
31
46
  # objects.
32
47
 
33
48
  class Client
34
-
35
49
  extend Forwardable
36
50
 
37
51
  # The OAuth::Consumer instance returned by the OauthClient
@@ -43,27 +57,79 @@ module JIRA
43
57
  # The configuration options for this client instance
44
58
  attr_reader :options
45
59
 
46
- def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token
60
+ def_delegators :@request_client, :init_access_token, :set_access_token, :set_request_token, :request_token, :access_token, :authenticated?
61
+
62
+ DEFINED_OPTIONS = [
63
+ :site,
64
+ :context_path,
65
+ :signature_method,
66
+ :request_token_path,
67
+ :authorize_path,
68
+ :access_token_path,
69
+ :private_key,
70
+ :private_key_file,
71
+ :rest_base_path,
72
+ :consumer_key,
73
+ :consumer_secret,
74
+ :ssl_verify_mode,
75
+ :ssl_version,
76
+ :use_ssl,
77
+ :username,
78
+ :password,
79
+ :auth_type,
80
+ :proxy_address,
81
+ :proxy_port,
82
+ :proxy_username,
83
+ :proxy_password,
84
+ :use_cookies,
85
+ :additional_cookies,
86
+ :default_headers,
87
+ :use_client_cert,
88
+ :read_timeout,
89
+ :http_debug,
90
+ :issuer,
91
+ :base_url,
92
+ :shared_secret,
93
+ :cert_path,
94
+ :key_path,
95
+ :ssl_client_cert,
96
+ :ssl_client_key
97
+ ].freeze
47
98
 
48
99
  DEFAULT_OPTIONS = {
49
- :site => 'http://localhost:2990',
50
- :context_path => '/jira',
51
- :rest_base_path => "/rest/api/2",
52
- :ssl_verify_mode => OpenSSL::SSL::VERIFY_PEER,
53
- :use_ssl => true,
54
- :auth_type => :oauth,
55
- :http_debug => false
56
- }
57
-
58
- def initialize(options={})
100
+ site: 'http://localhost:2990',
101
+ context_path: '/jira',
102
+ rest_base_path: '/rest/api/2',
103
+ ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
104
+ use_ssl: true,
105
+ use_client_cert: false,
106
+ auth_type: :oauth,
107
+ http_debug: false,
108
+ default_headers: {}
109
+ }.freeze
110
+
111
+ def initialize(options = {})
59
112
  options = DEFAULT_OPTIONS.merge(options)
60
113
  @options = options
61
114
  @options[:rest_base_path] = @options[:context_path] + @options[:rest_base_path]
62
115
 
116
+ unknown_options = options.keys.reject { |o| DEFINED_OPTIONS.include?(o) }
117
+ raise ArgumentError, "Unknown option(s) given: #{unknown_options}" unless unknown_options.empty?
118
+
119
+ if options[:use_client_cert]
120
+ @options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(File.read(@options[:cert_path])) if @options[:cert_path]
121
+ @options[:ssl_client_key] = OpenSSL::PKey::RSA.new(File.read(@options[:key_path])) if @options[:key_path]
122
+
123
+ raise ArgumentError, 'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true' unless @options[:ssl_client_cert]
124
+ raise ArgumentError, 'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true' unless @options[:ssl_client_key]
125
+ end
126
+
63
127
  case options[:auth_type]
64
- when :oauth
128
+ when :oauth, :oauth_2legged
65
129
  @request_client = OauthClient.new(@options)
66
130
  @consumer = @request_client.consumer
131
+ when :jwt
132
+ @request_client = JwtClient.new(@options)
67
133
  when :basic
68
134
  @request_client = HttpClient.new(@options)
69
135
  when :cookie
@@ -71,8 +137,10 @@ module JIRA
71
137
  @options[:use_cookies] = true
72
138
  @request_client = HttpClient.new(@options)
73
139
  @request_client.make_cookie_auth_request
140
+ @options.delete(:username)
141
+ @options.delete(:password)
74
142
  else
75
- raise ArgumentError, 'Options: ":auth_type" must be ":oauth", ":cookie" or ":basic"'
143
+ raise ArgumentError, 'Options: ":auth_type" must be ":oauth",":oauth_2legged", ":cookie" or ":basic"'
76
144
  end
77
145
 
78
146
  @http_debug = @options[:http_debug]
@@ -142,10 +210,26 @@ module JIRA
142
210
  JIRA::Resource::FieldFactory.new(self)
143
211
  end
144
212
 
213
+ def Board
214
+ JIRA::Resource::BoardFactory.new(self)
215
+ end
216
+
217
+ def BoardConfiguration
218
+ JIRA::Resource::BoardConfigurationFactory.new(self)
219
+ end
220
+
145
221
  def RapidView
146
222
  JIRA::Resource::RapidViewFactory.new(self)
147
223
  end
148
224
 
225
+ def Sprint
226
+ JIRA::Resource::SprintFactory.new(self)
227
+ end
228
+
229
+ def SprintReport
230
+ JIRA::Resource::SprintReportFactory.new(self)
231
+ end
232
+
149
233
  def ServerInfo
150
234
  JIRA::Resource::ServerInfoFactory.new(self)
151
235
  end
@@ -158,6 +242,10 @@ module JIRA
158
242
  JIRA::Resource::ApplicationLinkFactory.new(self)
159
243
  end
160
244
 
245
+ def Watcher
246
+ JIRA::Resource::WatcherFactory.new(self)
247
+ end
248
+
161
249
  def Webhook
162
250
  JIRA::Resource::WebhookFactory.new(self)
163
251
  end
@@ -170,12 +258,12 @@ module JIRA
170
258
  JIRA::Resource::IssuelinktypeFactory.new(self)
171
259
  end
172
260
 
173
- def Remotelink
174
- JIRA::Resource::RemotelinkFactory.new(self)
261
+ def IssuePickerSuggestions
262
+ JIRA::Resource::IssuePickerSuggestionsFactory.new(self)
175
263
  end
176
264
 
177
- def Sprint
178
- JIRA::Resource::SprintFactory.new(self)
265
+ def Remotelink
266
+ JIRA::Resource::RemotelinkFactory.new(self)
179
267
  end
180
268
 
181
269
  def Agile
@@ -197,27 +285,36 @@ module JIRA
197
285
 
198
286
  # HTTP methods with a body
199
287
  def post(path, body = '', headers = {})
200
- headers = {'Content-Type' => 'application/json'}.merge(headers)
288
+ headers = { 'Content-Type' => 'application/json' }.merge(headers)
201
289
  request(:post, path, body, merge_default_headers(headers))
202
290
  end
203
291
 
292
+ def post_multipart(path, file, headers = {})
293
+ puts "post multipart: #{path} - [#{file}]" if @http_debug
294
+ @request_client.request_multipart(path, file, headers)
295
+ end
296
+
204
297
  def put(path, body = '', headers = {})
205
- headers = {'Content-Type' => 'application/json'}.merge(headers)
298
+ headers = { 'Content-Type' => 'application/json' }.merge(headers)
206
299
  request(:put, path, body, merge_default_headers(headers))
207
300
  end
208
301
 
209
302
  # Sends the specified HTTP request to the REST API through the
210
303
  # appropriate method (oauth, basic).
211
- def request(http_method, path, body = '', headers={})
304
+ def request(http_method, path, body = '', headers = {})
212
305
  puts "#{http_method}: #{path} - [#{body}]" if @http_debug
213
306
  @request_client.request(http_method, path, body, headers)
214
307
  end
215
308
 
216
- protected
309
+ # Stops sensitive client information from being displayed in logs
310
+ def inspect
311
+ "#<JIRA::Client:#{object_id}>"
312
+ end
217
313
 
218
- def merge_default_headers(headers)
219
- {'Accept' => 'application/json'}.merge(headers)
220
- end
314
+ protected
221
315
 
316
+ def merge_default_headers(headers)
317
+ { 'Accept' => 'application/json' }.merge(@options[:default_headers]).merge(headers)
318
+ end
222
319
  end
223
320
  end
@@ -7,7 +7,6 @@
7
7
  # In practice, instances of this class behave exactly like an Array.
8
8
  #
9
9
  class JIRA::HasManyProxy
10
-
11
10
  attr_reader :target_class, :parent
12
11
  attr_accessor :collection
13
12