jira-ruby 3.0.0.beta1 → 3.0.0.beta2

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +1 -0
  3. data/.github/workflows/codeql.yml +0 -4
  4. data/.gitignore +3 -1
  5. data/.rubocop.yml +1 -69
  6. data/.yardopts +4 -0
  7. data/lib/jira/base.rb +5 -13
  8. data/lib/jira/client.rb +59 -4
  9. data/lib/jira/has_many_proxy.rb +30 -28
  10. data/lib/jira/http_client.rb +64 -1
  11. data/lib/jira/oauth_client.rb +62 -0
  12. data/lib/jira/request_client.rb +26 -1
  13. data/lib/jira/resource/attachment.rb +88 -3
  14. data/lib/jira/resource/field.rb +4 -8
  15. data/lib/jira/resource/issue.rb +64 -4
  16. data/lib/jira/resource/issue_picker_suggestions.rb +1 -1
  17. data/lib/jira/resource/issuelink.rb +4 -3
  18. data/lib/jira/resource/watcher.rb +1 -1
  19. data/lib/jira/resource/webhook.rb +5 -1
  20. data/lib/jira/version.rb +1 -1
  21. data/lib/tasks/generate.rake +1 -1
  22. data/spec/integration/project_spec.rb +1 -1
  23. data/spec/integration/rapidview_spec.rb +1 -1
  24. data/spec/integration/user_spec.rb +12 -3
  25. data/spec/integration/watcher_spec.rb +6 -2
  26. data/spec/integration/{webhook.rb → webhook_spec.rb} +8 -1
  27. data/spec/jira/base_factory_spec.rb +11 -2
  28. data/spec/jira/base_spec.rb +80 -57
  29. data/spec/jira/client_spec.rb +20 -18
  30. data/spec/jira/http_client_spec.rb +2 -2
  31. data/spec/jira/oauth_client_spec.rb +8 -4
  32. data/spec/jira/resource/agile_spec.rb +2 -2
  33. data/spec/jira/resource/attachment_spec.rb +36 -13
  34. data/spec/jira/resource/board_spec.rb +5 -5
  35. data/spec/jira/resource/field_spec.rb +23 -24
  36. data/spec/jira/resource/issue_spec.rb +18 -18
  37. data/spec/jira/resource/project_spec.rb +6 -6
  38. data/spec/jira/resource/sprint_spec.rb +20 -8
  39. data/spec/jira/resource/status_spec.rb +1 -1
  40. data/spec/jira/resource/user_factory_spec.rb +2 -2
  41. data/spec/jira/resource/worklog_spec.rb +1 -1
  42. data/spec/support/clients_helper.rb +2 -2
  43. data/spec/support/mock_client.rb +9 -0
  44. data/spec/support/mock_response.rb +8 -0
  45. metadata +9 -10
@@ -5,7 +5,16 @@ require 'json'
5
5
  require 'forwardable'
6
6
 
7
7
  module JIRA
8
+ # Client using OAuth 1.0
9
+ #
10
+ # @!attribute [rw] consumer
11
+ # @return [OAuth::Consumer] The oauth consumer object
12
+ # @!attribute [r] options
13
+ # @return [Hash] The oauth connection options
14
+ # @!attribute [r] access_token
15
+ # @return [OAuth::AccessToken] The oauth access token
8
16
  class OauthClient < RequestClient
17
+ # @private
9
18
  DEFAULT_OPTIONS = {
10
19
  signature_method: 'RSA-SHA1',
11
20
  request_token_path: '/plugins/servlet/oauth/request-token',
@@ -31,11 +40,29 @@ module JIRA
31
40
 
32
41
  def_instance_delegators :@consumer, :key, :secret, :get_request_token
33
42
 
43
+ # Generally not used directly, but through JIRA::Client.
44
+ # @param [Hash] options Options as passed from JIRA::Client constructor.
45
+ # @option options [String] :signature_method The signature method to use (defaults to 'RSA-SHA1')
46
+ # @option options [String] :request_token_path The path to request a token (defaults to '/plugins/servlet/oauth/request-token')
47
+ # @option options [String] :authorize_path The path to authorize a token (defaults to '/plugins/servlet/oauth/authorize')
48
+ # @option options [String] :access_token_path The path to access a token (defaults to '/plugins/servlet/oauth/access-token')
49
+ # @option options [String] :private_key_file The path to the private key file
50
+ # @option options [String] :consumer_key The OAuth 1.0 consumer key
51
+ # @option options [String] :consumer_secret The OAuth 1.0 consumer secret
52
+ # @option options [Hash] :default_headers Additional headers for requests
53
+ # @option options [String] :proxy_uri Proxy URI
54
+ # @option options [String] :proxy_user Proxy user
55
+ # @option options [String] :proxy_password Proxy Password
34
56
  def initialize(options)
35
57
  @options = DEFAULT_OPTIONS.merge(options)
36
58
  @consumer = init_oauth_consumer(@options)
37
59
  end
38
60
 
61
+ # @private
62
+ # Initialises the OAuth consumer object.
63
+ # Generally you should not call this method directly, it is called by the constructor.
64
+ # @param [Hash] _options The options hash
65
+ # @return [OAuth::Consumer] The OAuth consumer object
39
66
  def init_oauth_consumer(_options)
40
67
  @options[:request_token_path] = @options[:context_path] + @options[:request_token_path]
41
68
  @options[:authorize_path] = @options[:context_path] + @options[:authorize_path]
@@ -47,22 +74,31 @@ module JIRA
47
74
 
48
75
  # Returns the current request token if it is set, else it creates
49
76
  # and sets a new token.
77
+ # @param [Hash] options
50
78
  def request_token(options = {}, ...)
51
79
  @request_token ||= get_request_token(options, ...)
52
80
  end
53
81
 
54
82
  # Sets the request token from a given token and secret.
83
+ # @param [String] token The request token
84
+ # @param [String] secret The request token secret
85
+ # @return [OAuth::RequestToken] The request token object
55
86
  def set_request_token(token, secret)
56
87
  @request_token = OAuth::RequestToken.new(@consumer, token, secret)
57
88
  end
58
89
 
59
90
  # Initialises and returns a new access token from the params hash
60
91
  # returned by the OAuth transaction.
92
+ # @param [Hash] params The params hash returned by the OAuth transaction
93
+ # @return [OAuth::AccessToken] The access token object
61
94
  def init_access_token(params)
62
95
  @access_token = request_token.get_access_token(params)
63
96
  end
64
97
 
65
98
  # Sets the access token from a preexisting token and secret.
99
+ # @param [String] token The access token
100
+ # @param [String] secret The access token secret
101
+ # @return [OAuth::AccessToken] The access token object
66
102
  def set_access_token(token, secret)
67
103
  @access_token = OAuth::AccessToken.new(@consumer, token, secret)
68
104
  @authenticated = true
@@ -71,12 +107,25 @@ module JIRA
71
107
 
72
108
  # Returns the current access token. Raises an
73
109
  # JIRA::Client::UninitializedAccessTokenError exception if it is not set.
110
+ # @return [OAuth::AccessToken] The access token object
74
111
  def access_token
75
112
  raise UninitializedAccessTokenError unless @access_token
76
113
 
77
114
  @access_token
78
115
  end
79
116
 
117
+ # Makes a request to the JIRA server using the oauth gem.
118
+ #
119
+ # Generally you should not call this method directly, but use the helper methods in JIRA::Client.
120
+ #
121
+ # File uploads are not supported with this method. Use make_multipart_request instead.
122
+ #
123
+ # @param [Symbol] http_method The HTTP method to use
124
+ # @param [String] url The JIRA REST URL to call
125
+ # @param [String] body The body of the request
126
+ # @param [Hash] headers The headers to send with the request
127
+ # @return [Net::HTTPResponse] The response object
128
+ # @raise [JIRA::HTTPError] If the response is not an HTTP success code
80
129
  def make_request(http_method, url, body = '', headers = {})
81
130
  # When using oauth_2legged we need to add an empty oauth_token parameter to every request.
82
131
  if @options[:auth_type] == :oauth_2legged
@@ -100,6 +149,17 @@ module JIRA
100
149
  response
101
150
  end
102
151
 
152
+ # Makes a multipart request to the JIRA server using the oauth gem.
153
+ #
154
+ # This is used for file uploads.
155
+ #
156
+ # Generally you should not call this method directly, but use the helper methods in JIRA::Client.
157
+ #
158
+ # @param [String] url The JIRA REST URL to call
159
+ # @param [Hash] data The Net::HTTP::Post::Multipart data to send with the request
160
+ # @param [Hash] headers The headers to send with the request
161
+ # @return [Net::HTTPResponse] The response object
162
+ # @raise [JIRA::HTTPError] If the response is not an HTTP success code
103
163
  def make_multipart_request(url, data, headers = {})
104
164
  request = Net::HTTP::Post::Multipart.new url, data, headers
105
165
 
@@ -110,6 +170,8 @@ module JIRA
110
170
  response
111
171
  end
112
172
 
173
+ # Returns true if the client is authenticated.
174
+ # @return [Boolean] True if the client is authenticated
113
175
  def authenticated?
114
176
  @authenticated
115
177
  end
@@ -5,11 +5,21 @@ require 'json'
5
5
  require 'net/https'
6
6
 
7
7
  module JIRA
8
+ # Base class for request clients specific to a particular authentication method.
8
9
  class RequestClient
10
+ # Makes the JIRA REST API call.
11
+ #
9
12
  # Returns the response if the request was successful (HTTP::2xx) and
10
13
  # raises a JIRA::HTTPError if it was not successful, with the response
11
14
  # attached.
12
-
15
+ #
16
+ # Generally you should not call this method directly, but use derived classes.
17
+ #
18
+ # File uploads are not supported with this method. Use request_multipart instead.
19
+ #
20
+ # @param [Array] args Arguments to pass to the request method
21
+ # @return [Net::HTTPResponse] The response from the server
22
+ # @raise [JIRA::HTTPError] if it was not successful
13
23
  def request(*args)
14
24
  response = make_request(*args)
15
25
  raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
@@ -17,6 +27,15 @@ module JIRA
17
27
  response
18
28
  end
19
29
 
30
+ # Makes a multipart request to the JIRA server.
31
+ #
32
+ # This is used for file uploads.
33
+ #
34
+ # Generally you should not call this method directly, but use derived classes.
35
+ #
36
+ # @param [Array] args Arguments to pass to the request method
37
+ # @return [Net::HTTPResponse] The response from the server
38
+ # @raise [JIRA::HTTPError] if it was not successful
20
39
  def request_multipart(*args)
21
40
  response = make_multipart_request(*args)
22
41
  raise HTTPError, response unless response.is_a?(Net::HTTPSuccess)
@@ -24,10 +43,16 @@ module JIRA
24
43
  response
25
44
  end
26
45
 
46
+ # Abstract method to make a request to the JIRA server.
47
+ # @abstract
48
+ # @param [Array] args Arguments to pass to the request method
27
49
  def make_request(*args)
28
50
  raise NotImplementedError
29
51
  end
30
52
 
53
+ # Abstract method to make a request to the JIRA server with a file upload.
54
+ # @abstract
55
+ # @param [Array] args Arguments to pass to the request method
31
56
  def make_multipart_request(*args)
32
57
  raise NotImplementedError
33
58
  end
@@ -9,14 +9,85 @@ module JIRA
9
9
  delegate_to_target_class :meta
10
10
  end
11
11
 
12
+ # This class provides the Attachment object <-> REST mapping for JIRA::Resource::Attachment derived class,
13
+ # i.e. the Create, Retrieve, Update, Delete lifecycle methods.
14
+ #
15
+ # == Lifecycle methods
16
+ #
17
+ # === Retrieving a single attachment by Issue and attachment id
18
+ #
19
+ # # Find attachment with id 30076 on issue SUP-3000.
20
+ # issue = JIRA::Resource::Issue.find(client, 'SUP-3000', { fields: 'attachment' } )
21
+ # attachment = issue.attachments.find do |attachment_curr|
22
+ # 30076 == attachment_curr.id.to_i
23
+ # end
24
+ #
25
+ # === Retrieving meta information for an attachments
26
+ #
27
+ # attachment.meta
28
+ #
29
+ # === Retrieving file contents of attachment
30
+ #
31
+ # content = URI.open(attachment.content).read
32
+ # content = attachment.download_contents
33
+ # content = attachment.download_file { |file| file.read }
34
+ #
35
+ # === Adding an attachment to an issue
36
+ #
37
+ # Dir.mktmpdir do |dir|
38
+ # path = File.join(dir, filename)
39
+ # IO.copy_stream(file.path, path)
40
+ #
41
+ # issue = JIRA::Resource::Issue.find(client, 'SUP-3000', { fields: 'attachment' } )
42
+ # attachment = issue.attachments.build
43
+ # attachment.save!( { file: path, mimeType: content_type } )
44
+ # end
45
+ #
46
+ # === Deleting an attachment
47
+ #
48
+ # attachment.delete
49
+ #
50
+ # @!method save(attrs, path = url)
51
+ # Uploads a file as an attachment to its issue.
52
+ #
53
+ # Filename used will be the basename of the given file.
54
+ #
55
+ # @param [Hash] attrs the options to create a message with.
56
+ # @option attrs [IO,String] :file The file to upload, either a file object or a file path to find the file.
57
+ # @option attrs [String] :mimeType The MIME type of the file.
58
+ # @return [Boolean] True if successful, false if failed.
59
+ #
60
+ # @!attribute [r] self
61
+ # @return [String] URL to JSON of this attachment
62
+ # @!attribute [r] filename
63
+ # @return [String] the filename
64
+ # @!attribute [r] author
65
+ # @return [JIRA::Resource::User] the user who created the attachment
66
+ # @!attribute [r] created
67
+ # @return [String] timestamp when the attachment was created
68
+ # @!attribute [r] size
69
+ # @return [Integer] the file size
70
+ # @!attribute [r] mimeType
71
+ # @return [String] MIME of the content type
72
+ # @!attribute [r] content
73
+ # @return [String] URL (not the contents) to download the contents of the attachment
74
+ # @!attribute [r] thumbnail
75
+ # @return [String] URL to download the thumbnail of the attachment
76
+ #
12
77
  class Attachment < JIRA::Base
13
78
  belongs_to :issue
14
79
  has_one :author, class: JIRA::Resource::User
15
80
 
81
+ # @private
16
82
  def self.endpoint_name
17
83
  'attachments'
18
84
  end
19
85
 
86
+ # Gets metadata about attachments on the server.
87
+ # @example Return metadata
88
+ # Attachment.meta(client)
89
+ # => { "enabled" => true, "uploadLimit" => 1000000 }
90
+ # @return [Hash] The metadata for attachments. (See example.)
20
91
  def self.meta(client)
21
92
  response = client.get("#{client.options[:rest_base_path]}/attachment/meta")
22
93
  parse_json(response.body)
@@ -42,23 +113,37 @@ module JIRA
42
113
  # @param [Hash] headers Any additional headers to call Jira.
43
114
  # @yield |file|
44
115
  # @yieldparam [IO] file The IO object streaming the download.
45
- def download_file(headers = {}, &block)
116
+ def download_file(headers = {}, &)
46
117
  default_headers = client.options[:default_headers]
47
- URI.parse(content).open(default_headers.merge(headers), &block)
118
+ URI.parse(content).open(default_headers.merge(headers), &)
48
119
  end
49
120
 
50
121
  # Downloads the file contents as a string object.
51
122
  #
52
123
  # Note that this reads the contents into a ruby string in memory.
53
- # A file might be very large so it is recommend to avoid this unless you are certain about doing so.
124
+ # A file might be very large so it is recommended to avoid this unless you are certain about doing so.
54
125
  # Use the download_file method instead and avoid calling the read method without a limit.
55
126
  #
127
+ # @example Save file contents to a string.
128
+ # content = download_contents
56
129
  # @param [Hash] headers Any additional headers to call Jira.
57
130
  # @return [String,NilClass] The file contents.
58
131
  def download_contents(headers = {})
59
132
  download_file(headers, &:read)
60
133
  end
61
134
 
135
+ # Uploads a file as an attachment to its issue.
136
+ #
137
+ # Filename used will be the basename of the given file.
138
+ #
139
+ # @example Save a file as an attachment
140
+ # issue = JIRA::Resource::Issue.find(client, 'SUP-3000', { fields: 'attachment' } )
141
+ # attachment = issue.attachments.build
142
+ # attachment.save!( { file: path, mimeType: 'text/plain' } )
143
+ # @param [Hash] attrs the options to create a message with.
144
+ # @option attrs [IO,String] :file The file to upload, either a file object or a file path to find the file.
145
+ # @option attrs [String] :mimeType The MIME type of the file.
146
+ # @raise [JIRA::HTTPError] if failed
62
147
  def save!(attrs, path = url)
63
148
  file = attrs['file'] || attrs[:file] # Keep supporting 'file' parameter as a string for backward compatibility
64
149
  mime_type = attrs[:mimeType] || 'application/binary'
@@ -21,7 +21,6 @@ module JIRA
21
21
 
22
22
  def self.map_fields(client)
23
23
  field_map = {}
24
- field_map_reverse = {}
25
24
  fields = client.Field.all
26
25
 
27
26
  # two pass approach, so that a custom field with the same name
@@ -30,7 +29,6 @@ module JIRA
30
29
  next if f.custom
31
30
 
32
31
  name = safe_name(f.name)
33
- field_map_reverse[f.id] = [f.name, name] # capture both the official name, and the mapped name
34
32
  field_map[name] = f.id
35
33
  end
36
34
 
@@ -44,23 +42,21 @@ module JIRA
44
42
  else
45
43
  safe_name(f.name)
46
44
  end
47
- field_map_reverse[f.id] = [f.name, name] # capture both the official name, and the mapped name
48
45
  field_map[name] = f.id
49
46
  end
50
47
 
51
- client.cache.field_map_reverse = field_map_reverse # not sure where this will be used yet, but sure to be useful
52
- client.cache.field_map = field_map
48
+ client.field_map_cache = field_map
53
49
  end
54
50
 
55
51
  def self.field_map(client)
56
- client.cache.field_map
52
+ client.field_map_cache
57
53
  end
58
54
 
59
55
  def self.name_to_id(client, field_name)
60
56
  field_name = field_name.to_s
61
- return field_name unless client.cache.field_map && client.cache.field_map[field_name]
57
+ return field_name unless client.field_map_cache && client.field_map_cache[field_name]
62
58
 
63
- client.cache.field_map[field_name]
59
+ client.field_map_cache[field_name]
64
60
  end
65
61
 
66
62
  def respond_to?(method_name, _include_all = false)
@@ -8,6 +8,35 @@ module JIRA
8
8
  class IssueFactory < JIRA::BaseFactory # :nodoc:
9
9
  end
10
10
 
11
+ # This class provides the Issue object <-> REST mapping for JIRA::Resource::Issue derived class,
12
+ # i.e. the Create, Retrieve, Update, Delete lifecycle methods.
13
+ #
14
+ # == Lifecycle methods
15
+ #
16
+ # === Retrieving all issues
17
+ #
18
+ # client.Issue.all
19
+ #
20
+ # === Retrieving a single issue
21
+ #
22
+ # options = { expand: 'editmeta' }
23
+ # issue = client.Issue.find("SUP-3000", options)
24
+ #
25
+ # === Creating a new issue
26
+ #
27
+ # issue = client.Issue.build(fields: { summary: 'New issue', project: { key: 'SUP' }, issuetype: { name: 'Bug' } })
28
+ # issue.save
29
+ #
30
+ # === Updating an issue
31
+ #
32
+ # issue = client.Issue.find("SUP-3000")
33
+ # issue.save(fields: { summary: 'Updated issue' })
34
+ #
35
+ # === Deleting an issue
36
+ #
37
+ # issue = client.Issue.find("SUP-3000")
38
+ # issue.delete
39
+ #
11
40
  class Issue < JIRA::Base
12
41
  has_one :reporter, class: JIRA::Resource::User, nested_under: 'fields'
13
42
  has_one :assignee, class: JIRA::Resource::User, nested_under: 'fields'
@@ -29,6 +58,9 @@ module JIRA
29
58
  has_many :remotelink, class: JIRA::Resource::Remotelink
30
59
  has_many :watchers, attribute_key: 'watches', nested_under: %w[fields watches]
31
60
 
61
+ # Get collection of issues.
62
+ # @param client [JIRA::Client]
63
+ # @return [Array<JIRA::Resource::Issue>]
32
64
  def self.all(client)
33
65
  start_at = 0
34
66
  max_results = 1000
@@ -48,8 +80,7 @@ module JIRA
48
80
  result
49
81
  end
50
82
 
51
- def self.jql(client, jql, options = { fields: nil, start_at: nil, max_results: nil, expand: nil,
52
- validate_query: true })
83
+ def self.jql(client, jql, options = { fields: nil, start_at: nil, max_results: nil, expand: nil, validate_query: true })
53
84
  url = client.options[:rest_base_path] + "/search?jql=#{CGI.escape(jql)}"
54
85
 
55
86
  if options[:fields]
@@ -59,7 +90,7 @@ validate_query: true })
59
90
  end
60
91
  url << "&startAt=#{CGI.escape(options[:start_at].to_s)}" if options[:start_at]
61
92
  url << "&maxResults=#{CGI.escape(options[:max_results].to_s)}" if options[:max_results]
62
- url << '&validateQuery=false' if options[:validate_query] === false
93
+ url << '&validateQuery=false' if options[:validate_query] === false # rubocop:disable Style/CaseEquality
63
94
 
64
95
  if options[:expand]
65
96
  options[:expand] = [options[:expand]] if options[:expand].is_a?(String)
@@ -68,7 +99,7 @@ validate_query: true })
68
99
 
69
100
  response = client.get(url)
70
101
  json = parse_json(response.body)
71
- return json['total'] if options[:max_results] && (options[:max_results]).zero?
102
+ return json['total'] if options[:max_results]&.zero?
72
103
 
73
104
  json['issues'].map do |issue|
74
105
  client.Issue.build(issue)
@@ -78,6 +109,13 @@ validate_query: true })
78
109
  # Fetches the attributes for the specified resource from JIRA unless
79
110
  # the resource is already expanded and the optional force reload flag
80
111
  # is not set
112
+ # @param [Boolean] reload
113
+ # @param [Hash] query_params
114
+ # @option query_params [String] :fields
115
+ # @option query_params [String] :expand
116
+ # @option query_params [Integer] :startAt
117
+ # @option query_params [Integer] :maxResults
118
+ # @return [void]
81
119
  def fetch(reload = false, query_params = {})
82
120
  return if expanded? && !reload
83
121
 
@@ -101,6 +139,7 @@ validate_query: true })
101
139
  json['fields']
102
140
  end
103
141
 
142
+ # @private
104
143
  def respond_to?(method_name, _include_all = false)
105
144
  if attrs.key?('fields') && [method_name.to_s, client.Field.name_to_id(method_name)].any? do |k|
106
145
  attrs['fields'].key?(k)
@@ -111,6 +150,7 @@ validate_query: true })
111
150
  end
112
151
  end
113
152
 
153
+ # @private
114
154
  def method_missing(method_name, *args, &)
115
155
  if attrs.key?('fields')
116
156
  if attrs['fields'].key?(method_name.to_s)
@@ -127,6 +167,26 @@ validate_query: true })
127
167
  super
128
168
  end
129
169
  end
170
+
171
+ # @!method self.find(client, key, options = {})
172
+ # Gets/fetches an issue from JIRA.
173
+ #
174
+ # Note: attachments are not fetched by default.
175
+ #
176
+ # @param [JIRA::Client] client
177
+ # @param [String] key the key of the issue to find
178
+ # @param [Hash] options the options to find the issue with
179
+ # @option options [String] :fields the fields to include in the response
180
+ # @return [JIRA::Resource::Issue] the found issue
181
+ # @example Find an issue
182
+ # JIRA::Resource::Issue.find(client, "SUP-3000", { fields: %w[summary description attachment created ] } )
183
+ #
184
+ # @!method self.build(attrs = {})
185
+ # Constructs a new issue object.
186
+ # @param [Hash] attrs the attributes to initialize the issue with
187
+ # @return [JIRA::Resource::Issue] the new issue
188
+ #
189
+ # .
130
190
  end
131
191
  end
132
192
  end
@@ -9,7 +9,7 @@ module JIRA
9
9
  has_many :sections, class: JIRA::Resource::IssuePickerSuggestionsIssue
10
10
 
11
11
  def self.all(client, query = '', options = { current_jql: nil, current_issue_key: nil, current_project_id: nil,
12
- show_sub_tasks: nil, show_sub_tasks_parent: nil })
12
+ show_sub_tasks: nil, show_sub_tasks_parent: nil })
13
13
  url = client.options[:rest_base_path] + "/issue/picker?query=#{CGI.escape(query)}"
14
14
 
15
15
  url << "&currentJQL=#{CGI.escape(options[:current_jql])}" if options[:current_jql]
@@ -5,9 +5,10 @@ module JIRA
5
5
  class IssuelinkFactory < JIRA::BaseFactory # :nodoc:
6
6
  end
7
7
 
8
- # Because of circular dependency Issue->IssueLink->Issue
9
- # we have to declare JIRA::Resource::Issue class.
10
- class Issue < JIRA::Base; end
8
+ class Issue < JIRA::Base
9
+ # Because of circular dependency Issue->IssueLink->Issue
10
+ # we have to declare JIRA::Resource::Issue class.
11
+ end
11
12
 
12
13
  class Issuelink < JIRA::Base
13
14
  has_one :type, class: JIRA::Resource::Issuelinktype
@@ -28,7 +28,7 @@ module JIRA
28
28
 
29
29
  def save!(user_id, path = nil)
30
30
  path ||= new_record? ? url : patched_url
31
- response = client.post(path, user_id.to_json)
31
+ client.post(path, user_id.to_json)
32
32
  true
33
33
  end
34
34
  end
@@ -16,8 +16,12 @@ module JIRA
16
16
  client.options[:context_path] + REST_BASE_PATH
17
17
  end
18
18
 
19
+ def self.singular_path(client, key, prefix = '/')
20
+ "#{full_url(client)}#{prefix}/#{endpoint_name}/#{key}"
21
+ end
22
+
19
23
  def self.collection_path(client, prefix = '/')
20
- full_url(client) + prefix + endpoint_name
24
+ "#{full_url(client)}#{prefix}/#{endpoint_name}"
21
25
  end
22
26
 
23
27
  def self.all(client, options = {})
data/lib/jira/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JIRA
4
- VERSION = '3.0.0.beta1'
4
+ VERSION = '3.0.0.beta2'
5
5
  end
@@ -12,7 +12,7 @@ namespace :jira do
12
12
  desc 'Run the system call to generate a RSA public certificate'
13
13
  task :generate_public_cert do
14
14
  puts "Executing 'openssl req -x509 -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem'"
15
- system('openssl req -x509 -subj "/C=US/ST=New York/L=New York/O=SUMO Heavy Industries/CN=www.sumoheavy.com" -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem') # rubocop:disable Layout/LineLength
15
+ system('openssl req -x509 -subj "/C=US/ST=New York/L=New York/O=SUMO Heavy Industries/CN=www.sumoheavy.com" -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem')
16
16
  puts "Done. The RSA-SHA1 private keyfile is in the current directory: 'rsakey.pem'."
17
17
  puts 'You will need to copy the following certificate into your application link configuration in Jira:'
18
18
  system('cat rsacert.pem')
@@ -30,7 +30,7 @@ describe JIRA::Resource::Project do
30
30
  expect(issues.length).to eq(11)
31
31
  issues.each do |issue|
32
32
  expect(issue.class).to eq(JIRA::Resource::Issue)
33
- expect(issue.expanded?).to be_falsey
33
+ expect(issue).not_to be_expanded
34
34
  end
35
35
  end
36
36
  end
@@ -66,7 +66,7 @@ describe JIRA::Resource::RapidView do
66
66
 
67
67
  issues.each do |issue|
68
68
  expect(issue.class).to eq(JIRA::Resource::Issue)
69
- expect(issue.expanded?).to be_falsey
69
+ expect(issue).not_to be_expanded
70
70
  end
71
71
  end
72
72
  end
@@ -40,14 +40,23 @@ describe JIRA::Resource::User do
40
40
  end
41
41
 
42
42
  before do
43
+ user_factory = double('UserFactory')
44
+
43
45
  allow(client).to receive(:get)
44
- .with('/rest/api/2/users/search?username=_&maxResults=1000') { OpenStruct.new(body: '["User1"]') }
45
- allow(client).to receive_message_chain(:User, :build).with('users') { [] }
46
+ .with('/rest/api/2/users/search?username=_&maxResults=1000')
47
+ .and_return(double(body: '["User1"]'))
48
+ allow(client).to receive(:User).and_return(user_factory)
49
+ allow(user_factory).to receive(:build).with('users').and_return([])
46
50
  end
47
51
 
48
52
  it 'gets users with maxResults of 1000' do
53
+ user_factory = double('UserFactory')
54
+
49
55
  expect(client).to receive(:get).with('/rest/api/2/users/search?username=_&maxResults=1000')
50
- expect(client).to receive_message_chain(:User, :build).with('User1')
56
+ .and_return(double(body: '["User1"]'))
57
+ expect(client).to receive(:User).and_return(user_factory)
58
+ expect(user_factory).to receive(:build).with('User1')
59
+
51
60
  described_class.all(client)
52
61
  end
53
62
  end
@@ -46,15 +46,19 @@ describe JIRA::Resource::Watcher do
46
46
 
47
47
  it 'returnses all the watchers' do
48
48
  issue = client.Issue.find('10002')
49
- watchers = client.Watcher.all(options = { issue: })
49
+ watchers = client.Watcher.all({ issue: })
50
50
  expect(watchers.length).to eq(1)
51
51
  end
52
52
 
53
53
  it 'adds a watcher' do
54
54
  issue = client.Issue.find('10002')
55
- watcher = described_class.new(client, issue:)
55
+ watcher = described_class.new(client, issue: issue)
56
56
  user_id = 'tester'
57
+
57
58
  watcher.save!(user_id)
59
+
60
+ expect(WebMock).to have_requested(:post, "#{site_url}/jira/rest/api/2/issue/10002/watchers")
61
+ .with(body: '"tester"')
58
62
  end
59
63
  end
60
64
  end
@@ -9,7 +9,7 @@ describe JIRA::Resource::Webhook do
9
9
 
10
10
  let(:expected_attributes) do
11
11
  { 'name' => 'from API', 'url' => 'http://localhost:3000/webhooks/1', 'excludeBody' => false,
12
- 'filters' => { 'issue-related-events-section' => '' }, 'events' => [], 'enabled' => true, 'self' => 'http://localhost:2990/jira/rest/webhooks/1.0/webhook/2', 'lastUpdatedUser' => 'admin', 'lastUpdatedDisplayName' => 'admin', 'lastUpdated' => 1_453_306_520_188 }
12
+ 'filters' => { 'issue-related-events-section' => '' }, 'events' => [], 'enabled' => true, 'self' => 'http://localhost:2990/jira/rest/webhooks/1.0/webhook/2', 'lastUpdatedUser' => 'admin', 'lastUpdatedDisplayName' => 'admin', 'lastUpdated' => 1_453_306_520_188 }
13
13
  end
14
14
 
15
15
  let(:expected_collection_length) { 1 }
@@ -21,6 +21,13 @@ describe JIRA::Resource::Webhook do
21
21
  it 'returns a collection of components' do
22
22
  stub_request(:get, site_url + described_class.singular_path(client, key))
23
23
  .to_return(status: 200, body: get_mock_response('webhook/webhook.json'))
24
+
25
+ webhook = client.Webhook.find(key)
26
+
27
+ expect(webhook).to be_a described_class
28
+ expect(webhook.name).to eq 'from API'
29
+ expect(webhook.url).to eq '/jira/rest/webhooks/1.0/webhook/2'
30
+ expect(webhook.enabled).to be true
24
31
  end
25
32
  end
26
33
  end
@@ -1,8 +1,17 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe JIRA::BaseFactory do
4
- class JIRA::Resource::FooFactory < JIRA::BaseFactory; end
5
- class JIRA::Resource::Foo; end
4
+ module JIRA
5
+ module Resource
6
+ class FooFactory < JIRA::BaseFactory; end
7
+ end
8
+ end
9
+
10
+ module JIRA
11
+ module Resource
12
+ class Foo; end
13
+ end
14
+ end
6
15
 
7
16
  subject { JIRA::Resource::FooFactory.new(client) }
8
17