jira-ruby 2.3.0 → 3.2.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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. data/.github/dependabot.yml +6 -0
  5. data/.github/workflows/CI.yml +30 -0
  6. data/.github/workflows/codeql.yml +96 -0
  7. data/.github/workflows/rubocop.yml +18 -0
  8. data/.gitignore +3 -1
  9. data/.rubocop.yml +123 -0
  10. data/.yardopts +4 -0
  11. data/Gemfile +11 -3
  12. data/Guardfile +2 -0
  13. data/README.md +94 -19
  14. data/Rakefile +3 -4
  15. data/jira-ruby.gemspec +12 -17
  16. data/lib/jira/atlassian/jwt.rb +76 -0
  17. data/lib/jira/base.rb +37 -36
  18. data/lib/jira/base_factory.rb +4 -1
  19. data/lib/jira/client.rb +132 -51
  20. data/lib/jira/has_many_proxy.rb +32 -28
  21. data/lib/jira/http_client.rb +80 -13
  22. data/lib/jira/http_error.rb +4 -0
  23. data/lib/jira/jwt_client.rb +19 -43
  24. data/lib/jira/oauth_client.rb +68 -3
  25. data/lib/jira/railtie.rb +2 -0
  26. data/lib/jira/request_client.rb +31 -2
  27. data/lib/jira/resource/agile.rb +7 -9
  28. data/lib/jira/resource/applinks.rb +5 -3
  29. data/lib/jira/resource/attachment.rb +144 -4
  30. data/lib/jira/resource/board.rb +5 -3
  31. data/lib/jira/resource/board_configuration.rb +2 -0
  32. data/lib/jira/resource/comment.rb +14 -0
  33. data/lib/jira/resource/component.rb +2 -0
  34. data/lib/jira/resource/createmeta.rb +7 -5
  35. data/lib/jira/resource/field.rb +13 -12
  36. data/lib/jira/resource/filter.rb +2 -0
  37. data/lib/jira/resource/issue.rb +148 -54
  38. data/lib/jira/resource/issue_picker_suggestions.rb +4 -1
  39. data/lib/jira/resource/issue_picker_suggestions_issue.rb +2 -0
  40. data/lib/jira/resource/issuelink.rb +6 -3
  41. data/lib/jira/resource/issuelinktype.rb +2 -0
  42. data/lib/jira/resource/issuetype.rb +2 -0
  43. data/lib/jira/resource/priority.rb +2 -0
  44. data/lib/jira/resource/project.rb +4 -2
  45. data/lib/jira/resource/properties.rb +56 -0
  46. data/lib/jira/resource/rapidview.rb +5 -3
  47. data/lib/jira/resource/remotelink.rb +2 -0
  48. data/lib/jira/resource/resolution.rb +2 -0
  49. data/lib/jira/resource/serverinfo.rb +2 -0
  50. data/lib/jira/resource/sprint.rb +14 -23
  51. data/lib/jira/resource/status.rb +7 -1
  52. data/lib/jira/resource/status_category.rb +10 -0
  53. data/lib/jira/resource/suggested_issue.rb +2 -0
  54. data/lib/jira/resource/transition.rb +2 -0
  55. data/lib/jira/resource/user.rb +3 -1
  56. data/lib/jira/resource/version.rb +2 -0
  57. data/lib/jira/resource/watcher.rb +3 -2
  58. data/lib/jira/resource/webhook.rb +9 -3
  59. data/lib/jira/resource/worklog.rb +15 -2
  60. data/lib/jira/version.rb +3 -1
  61. data/lib/jira-ruby.rb +6 -3
  62. data/lib/tasks/generate.rake +3 -1
  63. data/spec/data/files/jwt-signed-urls.json +317 -0
  64. data/spec/data/files/short.txt +1 -0
  65. data/spec/integration/attachment_spec.rb +3 -3
  66. data/spec/integration/comment_spec.rb +8 -8
  67. data/spec/integration/component_spec.rb +7 -7
  68. data/spec/integration/field_spec.rb +3 -3
  69. data/spec/integration/issue_spec.rb +21 -18
  70. data/spec/integration/issuelinktype_spec.rb +3 -3
  71. data/spec/integration/issuetype_spec.rb +3 -3
  72. data/spec/integration/priority_spec.rb +3 -3
  73. data/spec/integration/project_spec.rb +8 -8
  74. data/spec/integration/properties_spec.rb +45 -0
  75. data/spec/integration/rapidview_spec.rb +10 -10
  76. data/spec/integration/resolution_spec.rb +3 -3
  77. data/spec/integration/status_category_spec.rb +20 -0
  78. data/spec/integration/status_spec.rb +4 -8
  79. data/spec/integration/transition_spec.rb +5 -4
  80. data/spec/integration/user_spec.rb +34 -11
  81. data/spec/integration/version_spec.rb +7 -7
  82. data/spec/integration/watcher_spec.rb +21 -18
  83. data/spec/integration/webhook_spec.rb +35 -0
  84. data/spec/integration/worklog_spec.rb +8 -8
  85. data/spec/jira/atlassian/jwt_spec.rb +60 -0
  86. data/spec/jira/base_factory_spec.rb +13 -3
  87. data/spec/jira/base_spec.rb +135 -98
  88. data/spec/jira/client_spec.rb +72 -56
  89. data/spec/jira/has_many_proxy_spec.rb +3 -3
  90. data/spec/jira/http_client_spec.rb +94 -27
  91. data/spec/jira/http_error_spec.rb +2 -2
  92. data/spec/jira/oauth_client_spec.rb +14 -8
  93. data/spec/jira/request_client_spec.rb +4 -4
  94. data/spec/jira/resource/agile_spec.rb +32 -32
  95. data/spec/jira/resource/attachment_spec.rb +170 -57
  96. data/spec/jira/resource/board_spec.rb +24 -23
  97. data/spec/jira/resource/createmeta_spec.rb +48 -48
  98. data/spec/jira/resource/field_spec.rb +44 -27
  99. data/spec/jira/resource/filter_spec.rb +7 -6
  100. data/spec/jira/resource/issue_picker_suggestions_spec.rb +17 -17
  101. data/spec/jira/resource/issue_spec.rb +159 -93
  102. data/spec/jira/resource/jira_picker_suggestions_issue_spec.rb +3 -3
  103. data/spec/jira/resource/project_factory_spec.rb +3 -2
  104. data/spec/jira/resource/project_spec.rb +16 -16
  105. data/spec/jira/resource/sprint_spec.rb +88 -9
  106. data/spec/jira/resource/status_spec.rb +21 -0
  107. data/spec/jira/resource/user_factory_spec.rb +5 -5
  108. data/spec/jira/resource/worklog_spec.rb +4 -4
  109. data/spec/mock_responses/board/1_issues.json +2 -1
  110. data/spec/mock_responses/issue/10002/properties/foo.json +4 -0
  111. data/spec/mock_responses/issue/10002/properties/xyz.json +4 -0
  112. data/spec/mock_responses/issue/10002/properties/xyz.put.json +4 -0
  113. data/spec/mock_responses/issue/10002/properties.json +12 -0
  114. data/spec/mock_responses/issue.json +1 -0
  115. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.full.json +2 -1
  116. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.json +2 -1
  117. data/spec/mock_responses/sprint/1.json +13 -0
  118. data/spec/mock_responses/status/1.json +8 -1
  119. data/spec/mock_responses/status.json +40 -5
  120. data/spec/mock_responses/statuscategory/1.json +7 -0
  121. data/spec/mock_responses/statuscategory.json +30 -0
  122. data/spec/mock_responses/{user_username=admin.json → user_accountId=1234567890abcdef01234567.json} +2 -1
  123. data/spec/spec_helper.rb +1 -0
  124. data/spec/support/clients_helper.rb +5 -7
  125. data/spec/support/mock_client.rb +9 -0
  126. data/spec/support/mock_response.rb +8 -0
  127. data/spec/support/shared_examples/integration.rb +51 -58
  128. metadata +45 -257
  129. data/.travis.yml +0 -9
  130. data/example.rb +0 -232
  131. data/http-basic-example.rb +0 -113
  132. data/lib/jira/resource/sprint_report.rb +0 -8
  133. data/lib/jira/tasks.rb +0 -0
  134. data/spec/integration/webhook.rb +0 -25
  135. data/spec/jira/jwt_uri_builder_spec.rb +0 -59
  136. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook.json +0 -11
  137. data/spec/mock_responses/webhook/webhook.json +0 -11
  138. /data/spec/mock_responses/{jira/rest/webhooks/1.0/webhook → webhook}/2.json +0 -0
@@ -7,25 +7,27 @@ describe JIRA::Resource::Issue do
7
7
  let(:client) do
8
8
  client = double(options: { rest_base_path: '/jira/rest/api/2' })
9
9
  allow(client).to receive(:Field).and_return(JIRA::Resource::FieldFactory.new(client))
10
- allow(client).to receive(:cache).and_return(OpenStruct.new)
10
+ allow(client).to receive(:field_map_cache).and_return(nil)
11
11
  client
12
12
  end
13
13
 
14
14
  describe '#respond_to?' do
15
15
  describe 'when decorated by SimpleDelegator' do
16
- before(:each) do
16
+ before do
17
17
  response = double
18
18
  allow(response).to receive(:body).and_return('{"key":"foo","id":"101"}')
19
- allow(JIRA::Resource::Issue).to receive(:collection_path).and_return('/jira/rest/api/2/issue')
19
+ allow(described_class).to receive(:collection_path).and_return('/jira/rest/api/2/issue')
20
20
  allow(client).to receive(:get).with('/jira/rest/api/2/issue/101')
21
21
  .and_return(response)
22
22
 
23
- issue = JIRA::Resource::Issue.find(client, 101)
23
+ issue = described_class.find(client, 101)
24
24
  @decorated = JIRAResourceDelegation.new(issue)
25
25
  end
26
+
26
27
  it 'responds to key' do
27
- expect(@decorated.respond_to?(:key)).to eq(true)
28
+ expect(@decorated.respond_to?(:key)).to be(true)
28
29
  end
30
+
29
31
  it 'does not raise an error' do
30
32
  expect do
31
33
  @issue.respond_to?(:project)
@@ -34,123 +36,179 @@ describe JIRA::Resource::Issue do
34
36
  end
35
37
  end
36
38
 
37
- it 'should find all issues' do
39
+ it 'finds all issues' do
38
40
  response = double
39
41
  empty_response = double
40
42
  issue = double
41
43
 
42
44
  allow(response).to receive(:body).and_return('{"issues":[{"id":"1","summary":"Bugs Everywhere"}]}')
43
- expect(client).to receive(:get).with('/jira/rest/api/2/search?expand=transitions.fields&maxResults=1000&startAt=0')
45
+ expect(client).to receive(:get).with('/jira/rest/api/2/search/jql?expand=transitions.fields&maxResults=1000&startAt=0')
44
46
  .and_return(response)
45
47
  allow(empty_response).to receive(:body).and_return('{"issues":[]}')
46
- expect(client).to receive(:get).with('/jira/rest/api/2/search?expand=transitions.fields&maxResults=1000&startAt=1')
48
+ expect(client).to receive(:get).with('/jira/rest/api/2/search/jql?expand=transitions.fields&maxResults=1000&startAt=1')
47
49
  .and_return(empty_response)
48
50
 
49
51
  expect(client).to receive(:Issue).and_return(issue)
50
52
  expect(issue).to receive(:build).with({ 'id' => '1', 'summary' => 'Bugs Everywhere' })
51
53
 
52
- issues = JIRA::Resource::Issue.all(client)
54
+ described_class.all(client)
53
55
  end
54
56
 
55
- it 'should find an issue by key or id' do
57
+ it 'finds an issue by key or id' do
56
58
  response = double
57
59
 
58
60
  allow(response).to receive(:body).and_return('{"key":"foo","id":"101"}')
59
- allow(JIRA::Resource::Issue).to receive(:collection_path).and_return('/jira/rest/api/2/issue')
61
+ allow(described_class).to receive(:collection_path).and_return('/jira/rest/api/2/issue')
60
62
  expect(client).to receive(:get).with('/jira/rest/api/2/issue/foo')
61
63
  .and_return(response)
62
64
  expect(client).to receive(:get).with('/jira/rest/api/2/issue/101')
63
65
  .and_return(response)
64
66
 
65
- issue_from_id = JIRA::Resource::Issue.find(client, 101)
66
- issue_from_key = JIRA::Resource::Issue.find(client, 'foo')
67
+ issue_from_id = described_class.find(client, 101)
68
+ issue_from_key = described_class.find(client, 'foo')
67
69
 
68
70
  expect(issue_from_id.attrs).to eq(issue_from_key.attrs)
69
71
  end
70
72
 
71
- it 'should search an issue with a jql query string' do
72
- response = double
73
- issue = double
73
+ describe '.jql' do
74
+ subject { described_class.jql(client, 'foo bar', args) }
74
75
 
75
- allow(response).to receive(:body).and_return('{"issues": {"key":"foo"}}')
76
- expect(client).to receive(:get).with('/jira/rest/api/2/search?jql=foo+bar')
77
- .and_return(response)
78
- expect(client).to receive(:Issue).and_return(issue)
79
- expect(issue).to receive(:build).with(%w[key foo]).and_return('')
76
+ let(:args) { {} }
77
+ let(:issue) { double }
78
+ let(:response) { double }
79
+ let(:response_string) { '{"issues": {"key":"foo"}, "isLast": true}' }
80
80
 
81
- expect(JIRA::Resource::Issue.jql(client, 'foo bar')).to eq([''])
82
- end
81
+ before do
82
+ allow(response).to receive(:body).and_return(response_string)
83
+ allow(client).to receive(:Issue).and_return(issue)
84
+ allow(issue).to receive(:build).with(%w[key foo]).and_return('')
85
+ end
83
86
 
84
- it 'should search an issue with a jql query string and fields' do
85
- response = double
86
- issue = double
87
+ it 'searches an issue with a jql query string' do
88
+ expect(client).to receive(:get).with('/jira/rest/api/2/search/jql?jql=foo+bar')
89
+ .and_return(response)
90
+ expect(described_class.jql(client, 'foo bar')).to eq([''])
91
+ end
87
92
 
88
- allow(response).to receive(:body).and_return('{"issues": {"key":"foo"}}')
89
- expect(client).to receive(:get)
90
- .with('/jira/rest/api/2/search?jql=foo+bar&fields=foo,bar')
91
- .and_return(response)
92
- expect(client).to receive(:Issue).and_return(issue)
93
- expect(issue).to receive(:build).with(%w[key foo]).and_return('')
93
+ it 'passes thorugh the reconcileIssues parameter' do
94
+ expect(client).to receive(:get)
95
+ .with('/jira/rest/api/2/search/jql?jql=foo+bar&reconcileIssues=true')
96
+ .and_return(response)
94
97
 
95
- expect(JIRA::Resource::Issue.jql(client, 'foo bar', fields: %w[foo bar])).to eq([''])
96
- end
98
+ expect(described_class.jql(client, 'foo bar', reconcile_issues: true)).to eq([''])
99
+ end
97
100
 
98
- it 'should search an issue with a jql query string, start at, and maxResults' do
99
- response = double
100
- issue = double
101
+ it 'searches an issue with a jql query string and fields' do
102
+ expect(client).to receive(:get)
103
+ .with('/jira/rest/api/2/search/jql?jql=foo+bar&fields=foo,bar')
104
+ .and_return(response)
101
105
 
102
- allow(response).to receive(:body).and_return('{"issues": {"key":"foo"}}')
103
- expect(client).to receive(:get)
104
- .with('/jira/rest/api/2/search?jql=foo+bar&startAt=1&maxResults=3')
105
- .and_return(response)
106
- expect(client).to receive(:Issue).and_return(issue)
107
- expect(issue).to receive(:build).with(%w[key foo]).and_return('')
106
+ expect(described_class.jql(client, 'foo bar', fields: %w[foo bar])).to eq([''])
107
+ end
108
108
 
109
- expect(JIRA::Resource::Issue.jql(client, 'foo bar', start_at: 1, max_results: 3)).to eq([''])
110
- end
109
+ context 'when maxResults is provided' do
110
+ let(:args) { { max_results: } }
111
111
 
112
- it 'should search an issue with a jql query string and maxResults equals zero and should return the count of tickets' do
113
- response = double
114
- issue = double
112
+ context 'with non-zero' do
113
+ let(:max_results) { 3 }
115
114
 
116
- allow(response).to receive(:body).and_return('{"total": 1, "issues": []}')
117
- expect(client).to receive(:get)
118
- .with('/jira/rest/api/2/search?jql=foo+bar&maxResults=0')
119
- .and_return(response)
115
+ it 'searches an issue with a jql query string and maxResults' do
116
+ expect(client).to receive(:get)
117
+ .with('/jira/rest/api/2/search/jql?jql=foo+bar&maxResults=3')
118
+ .and_return(response)
120
119
 
121
- expect(JIRA::Resource::Issue.jql(client, 'foo bar', max_results: 0)).to eq(1)
122
- end
120
+ expect(subject).to eq([''])
121
+ end
122
+ end
123
123
 
124
- it 'should search an issue with a jql query string and string expand' do
125
- response = double
126
- issue = double
124
+ context 'with zero' do
125
+ let(:response_string) { '{"total": 1, "issues": []}' }
126
+ let(:max_results) { 0 }
127
127
 
128
- allow(response).to receive(:body).and_return('{"issues": {"key":"foo"}}')
129
- expect(client).to receive(:get)
130
- .with('/jira/rest/api/2/search?jql=foo+bar&expand=transitions')
131
- .and_return(response)
132
- expect(client).to receive(:Issue).and_return(issue)
133
- expect(issue).to receive(:build).with(%w[key foo]).and_return('')
128
+ it 'searches an issue with a jql query string and should return the count of tickets' do
129
+ expect(client).to receive(:get)
130
+ .with('/jira/rest/api/2/search/jql?jql=foo+bar&maxResults=0')
131
+ .and_return(response)
132
+
133
+ expect(subject).to eq(1)
134
+ end
135
+ end
136
+ end
137
+
138
+ it 'searches an issue with a jql query string and string expand' do
139
+ expect(client).to receive(:get)
140
+ .with('/jira/rest/api/2/search/jql?jql=foo+bar&expand=transitions')
141
+ .and_return(response)
134
142
 
135
- expect(JIRA::Resource::Issue.jql(client, 'foo bar', expand: 'transitions')).to eq([''])
143
+ expect(described_class.jql(client, 'foo bar', expand: 'transitions')).to eq([''])
144
+ end
145
+
146
+ it 'searches an issue with a jql query string and array expand' do
147
+ expect(client).to receive(:get)
148
+ .with('/jira/rest/api/2/search/jql?jql=foo+bar&expand=transitions')
149
+ .and_return(response)
150
+
151
+ expect(described_class.jql(client, 'foo bar', expand: %w[transitions])).to eq([''])
152
+ end
153
+
154
+ context 'when pagination is required' do
155
+ let(:response_string) { '{"issues": [{"key":"foo"}], "isLast": false, "nextPageToken": "abc"}' }
156
+ let(:second_response_string) { '{"issues": [{"key":"bar"}], "isLast": true}' }
157
+
158
+ before do
159
+ allow(issue).to receive(:build).with({ 'key' => 'foo' }).and_return('1')
160
+ allow(issue).to receive(:build).with({ 'key' => 'bar' }).and_return('2')
161
+ end
162
+
163
+ it 'makes multiple requests' do
164
+ expect(client).to receive(:get)
165
+ .with('/jira/rest/api/2/search/jql?jql=foo+bar')
166
+ .and_return(response)
167
+ expect(client).to receive(:get)
168
+ .with('/jira/rest/api/2/search/jql?jql=foo+bar&nextPageToken=abc')
169
+ .and_return(double(body: second_response_string))
170
+
171
+ expect(subject).to eq(%w[1 2])
172
+ end
173
+ end
136
174
  end
137
175
 
138
- it 'should search an issue with a jql query string and array expand' do
139
- response = double
140
- issue = double
176
+ describe '.jql_paged' do
177
+ let(:issue) { double }
178
+ let(:response) { double }
141
179
 
142
- allow(response).to receive(:body).and_return('{"issues": {"key":"foo"}}')
143
- expect(client).to receive(:get)
144
- .with('/jira/rest/api/2/search?jql=foo+bar&expand=transitions')
145
- .and_return(response)
146
- expect(client).to receive(:Issue).and_return(issue)
147
- expect(issue).to receive(:build).with(%w[key foo]).and_return('')
180
+ before do
181
+ allow(response).to receive(:body).and_return(response_string)
182
+ allow(client).to receive(:Issue).and_return(issue)
183
+ allow(issue).to receive(:build).with({ 'key' => 'foo' }).and_return('1')
184
+ allow(issue).to receive(:build).with({ 'key' => 'bar' }).and_return('2')
185
+ allow(issue).to receive(:build).with({ 'key' => 'baz' }).and_return('3')
186
+ end
187
+
188
+ context 'without next_page_token (first page)' do
189
+ subject { described_class.jql_paged(client, 'foo bar', page_size: 2) }
190
+
191
+ before { expect(client).to receive(:get).with('/jira/rest/api/2/search/jql?jql=foo+bar').and_return(response) }
192
+
193
+ let(:response) { double }
194
+ let(:response_string) { '{"issues": [{"key":"foo"},{"key":"bar"}], "isLast": false, "nextPageToken": "abc"}' }
148
195
 
149
- expect(JIRA::Resource::Issue.jql(client, 'foo bar', expand: %w[transitions])).to eq([''])
196
+ it { is_expected.to eq(issues: %w[1 2], next_page_token: 'abc', total: nil) }
197
+ end
198
+
199
+ context 'with next_page_token' do
200
+ subject { described_class.jql_paged(client, 'foo bar', page_size: 2, next_page_token: 'abc') }
201
+
202
+ let(:response_string) { '{"issues": [{"key":"baz"}], "isLast": true}' }
203
+
204
+ before { expect(client).to receive(:get).with('/jira/rest/api/2/search/jql?jql=foo+bar&nextPageToken=abc').and_return(double(body: response_string)) }
205
+
206
+ it { is_expected.to eq(issues: %w[3], next_page_token: nil, total: nil) }
207
+ end
150
208
  end
151
209
 
152
- it 'should return meta data available for editing an issue' do
153
- subject = JIRA::Resource::Issue.new(client, attrs: { 'fields' => { 'key' => 'TST=123' } })
210
+ it 'returns meta data available for editing an issue' do
211
+ subject = described_class.new(client, attrs: { 'fields' => { 'key' => 'TST=123' } })
154
212
  response = double
155
213
 
156
214
  allow(response).to receive(:body).and_return(
@@ -164,29 +222,31 @@ describe JIRA::Resource::Issue do
164
222
  end
165
223
 
166
224
  it 'provides direct accessors to the fields' do
167
- subject = JIRA::Resource::Issue.new(client, attrs: { 'fields' => { 'foo' => 'bar' } })
225
+ subject = described_class.new(client, attrs: { 'fields' => { 'foo' => 'bar' } })
168
226
  expect(subject).to respond_to(:foo)
169
227
  expect(subject.foo).to eq('bar')
170
228
  end
171
229
 
172
230
  describe 'relationships' do
173
231
  subject do
174
- JIRA::Resource::Issue.new(client, attrs: {
175
- 'id' => '123',
176
- 'fields' => {
177
- 'reporter' => { 'foo' => 'bar' },
178
- 'assignee' => { 'foo' => 'bar' },
179
- 'project' => { 'foo' => 'bar' },
180
- 'priority' => { 'foo' => 'bar' },
181
- 'issuetype' => { 'foo' => 'bar' },
182
- 'status' => { 'foo' => 'bar' },
183
- 'components' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }],
184
- 'versions' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }],
185
- 'comment' => { 'comments' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }] },
186
- 'attachment' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }],
187
- 'worklog' => { 'worklogs' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }] }
188
- }
189
- })
232
+ described_class.new(client, attrs: {
233
+ 'id' => '123',
234
+ 'fields' => {
235
+ 'reporter' => { 'foo' => 'bar' },
236
+ 'assignee' => { 'foo' => 'bar' },
237
+ 'project' => { 'foo' => 'bar' },
238
+ 'priority' => { 'foo' => 'bar' },
239
+ 'issuetype' => { 'foo' => 'bar' },
240
+ 'status' => { 'foo' => 'bar' },
241
+ 'resolution' => { 'foo' => 'bar' },
242
+ 'components' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }],
243
+ 'versions' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }],
244
+ 'comment' => { 'comments' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }] },
245
+ 'attachment' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }],
246
+ 'worklog' => { 'worklogs' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }] }
247
+ },
248
+ 'properties' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }]
249
+ })
190
250
  end
191
251
 
192
252
  it 'has the correct relationships' do
@@ -208,6 +268,9 @@ describe JIRA::Resource::Issue do
208
268
  expect(subject).to have_one(:status, JIRA::Resource::Status)
209
269
  expect(subject.status.foo).to eq('bar')
210
270
 
271
+ expect(subject).to have_one(:resolution, JIRA::Resource::Resolution)
272
+ expect(subject.resolution.foo).to eq('bar')
273
+
211
274
  expect(subject).to have_many(:components, JIRA::Resource::Component)
212
275
  expect(subject.components.length).to eq(2)
213
276
 
@@ -222,6 +285,9 @@ describe JIRA::Resource::Issue do
222
285
 
223
286
  expect(subject).to have_many(:worklogs, JIRA::Resource::Worklog)
224
287
  expect(subject.worklogs.length).to eq(2)
288
+
289
+ expect(subject).to have_many(:properties, JIRA::Resource::Properties)
290
+ expect(subject.properties.length).to eq(2)
225
291
  end
226
292
  end
227
293
  end
@@ -5,9 +5,9 @@ describe JIRA::Resource::IssuePickerSuggestionsIssue do
5
5
 
6
6
  describe 'relationships' do
7
7
  subject do
8
- JIRA::Resource::IssuePickerSuggestionsIssue.new(client, attrs: {
9
- 'issues' => [{ 'id' => '1'}, { 'id' => '2' }]
10
- })
8
+ described_class.new(client, attrs: {
9
+ 'issues' => [{ 'id' => '1' }, { 'id' => '2' }]
10
+ })
11
11
  end
12
12
 
13
13
  it 'has the correct relationships' do
@@ -1,11 +1,12 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe JIRA::Resource::ProjectFactory do
4
+ subject { described_class.new(client) }
5
+
4
6
  let(:client) { double }
5
- subject { JIRA::Resource::ProjectFactory.new(client) }
6
7
 
7
8
  it 'initializes correctly' do
8
- expect(subject.class).to eq(JIRA::Resource::ProjectFactory)
9
+ expect(subject.class).to eq(described_class)
9
10
  expect(subject.client).to eq(client)
10
11
  end
11
12
  end
@@ -9,11 +9,11 @@ describe JIRA::Resource::Project do
9
9
 
10
10
  describe 'relationships' do
11
11
  subject do
12
- JIRA::Resource::Project.new(client, attrs: {
13
- 'lead' => { 'foo' => 'bar' },
14
- 'issueTypes' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }],
15
- 'versions' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }]
16
- })
12
+ described_class.new(client, attrs: {
13
+ 'lead' => { 'foo' => 'bar' },
14
+ 'issueTypes' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }],
15
+ 'versions' => [{ 'foo' => 'bar' }, { 'baz' => 'flum' }]
16
+ })
17
17
  end
18
18
 
19
19
  it 'has the correct relationships' do
@@ -30,9 +30,9 @@ describe JIRA::Resource::Project do
30
30
 
31
31
  describe 'issues' do
32
32
  subject do
33
- JIRA::Resource::Project.new(client, attrs: {
34
- 'key' => 'test'
35
- })
33
+ described_class.new(client, attrs: {
34
+ 'key' => 'test'
35
+ })
36
36
  end
37
37
 
38
38
  it 'returns issues' do
@@ -42,7 +42,7 @@ describe JIRA::Resource::Project do
42
42
  issue_factory = double('issue factory')
43
43
 
44
44
  expect(client).to receive(:get)
45
- .with('/jira/rest/api/2/search?jql=project%3D%22test%22')
45
+ .with('/jira/rest/api/2/search/jql?jql=project%3D%22test%22')
46
46
  .and_return(response)
47
47
  expect(client).to receive(:Issue).and_return(issue_factory)
48
48
  expect(issue_factory).to receive(:build)
@@ -58,7 +58,7 @@ describe JIRA::Resource::Project do
58
58
  issue_factory = double('issue factory')
59
59
 
60
60
  expect(client).to receive(:get)
61
- .with('/jira/rest/api/2/search?jql=project%3D%22test%22&expand=changelog&startAt=1&maxResults=100')
61
+ .with('/jira/rest/api/2/search/jql?jql=project%3D%22test%22&expand=changelog&startAt=1&maxResults=100')
62
62
  .and_return(response)
63
63
  expect(client).to receive(:Issue).and_return(issue_factory)
64
64
  expect(issue_factory).to receive(:build)
@@ -69,12 +69,12 @@ describe JIRA::Resource::Project do
69
69
  end
70
70
 
71
71
  describe 'users' do
72
- let(:project) { JIRA::Resource::Project.new(client, attrs: { 'key' => project_key }) }
72
+ let(:project) { described_class.new(client, attrs: { 'key' => project_key }) }
73
73
  let(:project_key) { SecureRandom.hex }
74
74
  let(:response) { double('response', body: '[{}]') }
75
75
 
76
- context 'pagination' do
77
- before(:each) do
76
+ context 'with pagination' do
77
+ before do
78
78
  user_factory = double('user factory')
79
79
  expect(client).to receive(:User).and_return(user_factory)
80
80
  expect(user_factory).to receive(:build).with(any_args)
@@ -95,7 +95,7 @@ describe JIRA::Resource::Project do
95
95
  .with("/jira/rest/api/2/user/assignable/search?project=#{project_key}&startAt=#{start_at}")
96
96
  .and_return(response)
97
97
 
98
- project.users(start_at: start_at)
98
+ project.users(start_at:)
99
99
  end
100
100
 
101
101
  it 'accepts max_results option' do
@@ -105,7 +105,7 @@ describe JIRA::Resource::Project do
105
105
  .with("/jira/rest/api/2/user/assignable/search?project=#{project_key}&maxResults=#{max_results}")
106
106
  .and_return(response)
107
107
 
108
- project.users(max_results: max_results)
108
+ project.users(max_results:)
109
109
  end
110
110
 
111
111
  it 'accepts start_at and max_results options' do
@@ -116,7 +116,7 @@ describe JIRA::Resource::Project do
116
116
  .with("/jira/rest/api/2/user/assignable/search?project=#{project_key}&startAt=#{start_at}&maxResults=#{max_results}")
117
117
  .and_return(response)
118
118
 
119
- project.users(start_at: start_at, max_results: max_results)
119
+ project.users(start_at:, max_results:)
120
120
  end
121
121
  end
122
122
  end
@@ -2,19 +2,32 @@ require 'spec_helper'
2
2
 
3
3
  describe JIRA::Resource::Sprint do
4
4
  let(:client) do
5
- client = double(options: { site: 'https://foo.bar.com', context_path: '/jira' })
5
+ client = double(options: { rest_base_path: '/jira/rest/api/2', context_path: '/jira' })
6
6
  allow(client).to receive(:Sprint).and_return(JIRA::Resource::SprintFactory.new(client))
7
7
  client
8
8
  end
9
9
  let(:sprint) { described_class.new(client) }
10
- let(:agile_sprint_path) { "#{sprint.client.options[:context_path]}/rest/agile/1.0/sprint/#{sprint.id}" }
10
+ let(:agile_sprint_path) { "/jira/rest/agile/1.0/sprint/#{sprint.id}" }
11
+ let(:response) { double }
12
+
13
+ describe 'get_sprint_details' do
14
+ let(:sprint) { described_class.find(client, '1') }
15
+
16
+ it 'check each of the date attributes' do
17
+ allow(client).to receive(:get).and_return(double(body: get_mock_response('sprint/1.json')))
18
+
19
+ expect(sprint.start_date).to eq Date.parse('2024-01-01T03:20:00.000Z')
20
+ expect(sprint.end_date).to eq Date.parse('2024-01-15T03:20:00.000Z')
21
+ expect(sprint.complete_date).to eq Date.parse('2024-01-16T03:48:00.000Z')
22
+ end
23
+ end
11
24
 
12
25
  describe '::find' do
13
26
  let(:response) { double('Response', body: '{"some_detail":"some detail"}') }
14
27
 
15
28
  it 'fetches the sprint from JIRA' do
16
29
  expect(client).to receive(:get).with('/jira/rest/agile/1.0/sprint/111').and_return(response)
17
- expect(JIRA::Resource::Sprint.find(client, '111')).to be_a(JIRA::Resource::Sprint)
30
+ expect(described_class.find(client, '111')).to be_a(described_class)
18
31
  end
19
32
  end
20
33
 
@@ -30,7 +43,9 @@ describe JIRA::Resource::Sprint do
30
43
  let(:given_attrs) { { start_date: '2016-06-10' } }
31
44
 
32
45
  it 'calls save on the super class with the given attributes & agile url' do
33
- expect_any_instance_of(JIRA::Base).to receive(:save).with(given_attrs, agile_sprint_path)
46
+ mock_response = double('response', body: '{"id":"123"}')
47
+
48
+ expect(client).to receive(:post).with(agile_sprint_path, given_attrs.to_json).and_return(mock_response)
34
49
 
35
50
  sprint.save(given_attrs)
36
51
  end
@@ -38,7 +53,9 @@ describe JIRA::Resource::Sprint do
38
53
 
39
54
  context 'when attributes are not specified' do
40
55
  it 'calls save on the super class with the instance attributes & agile url' do
41
- expect_any_instance_of(JIRA::Base).to receive(:save).with(instance_attrs, agile_sprint_path)
56
+ mock_response = double('response', body: '{"id":"123"}')
57
+
58
+ expect(client).to receive(:post).with(agile_sprint_path, instance_attrs.to_json).and_return(mock_response)
42
59
 
43
60
  sprint.save
44
61
  end
@@ -46,7 +63,9 @@ describe JIRA::Resource::Sprint do
46
63
 
47
64
  context 'when providing the path argument' do
48
65
  it 'ignores it' do
49
- expect_any_instance_of(JIRA::Base).to receive(:save).with(instance_attrs, agile_sprint_path)
66
+ mock_response = double('response', body: '{"id":"123"}')
67
+
68
+ expect(client).to receive(:post).with(agile_sprint_path, instance_attrs.to_json).and_return(mock_response)
50
69
 
51
70
  sprint.save({}, 'mavenlink.com')
52
71
  end
@@ -64,7 +83,9 @@ describe JIRA::Resource::Sprint do
64
83
  let(:given_attrs) { { start_date: '2016-06-10' } }
65
84
 
66
85
  it 'calls save! on the super class with the given attributes & agile url' do
67
- expect_any_instance_of(JIRA::Base).to receive(:save!).with(given_attrs, agile_sprint_path)
86
+ mock_response = double('response', body: '{"id":"123"}')
87
+
88
+ expect(client).to receive(:post).with(agile_sprint_path, given_attrs.to_json).and_return(mock_response)
68
89
 
69
90
  sprint.save!(given_attrs)
70
91
  end
@@ -72,7 +93,9 @@ describe JIRA::Resource::Sprint do
72
93
 
73
94
  context 'when attributes are not specified' do
74
95
  it 'calls save! on the super class with the instance attributes & agile url' do
75
- expect_any_instance_of(JIRA::Base).to receive(:save!).with(instance_attrs, agile_sprint_path)
96
+ mock_response = double('response', body: '{"id":"123"}')
97
+
98
+ expect(client).to receive(:post).with(agile_sprint_path, instance_attrs.to_json).and_return(mock_response)
76
99
 
77
100
  sprint.save!
78
101
  end
@@ -80,11 +103,67 @@ describe JIRA::Resource::Sprint do
80
103
 
81
104
  context 'when providing the path argument' do
82
105
  it 'ignores it' do
83
- expect_any_instance_of(JIRA::Base).to receive(:save!).with(instance_attrs, agile_sprint_path)
106
+ mock_response = double('response', body: '{"id":"123"}')
107
+
108
+ expect(client).to receive(:post).with(agile_sprint_path, instance_attrs.to_json).and_return(mock_response)
84
109
 
85
110
  sprint.save!({}, 'mavenlink.com')
86
111
  end
87
112
  end
88
113
  end
114
+
115
+ context 'when an issue exists' do
116
+ let(:issue_id) { 1001 }
117
+ let(:post_issue_path) do
118
+ described_class.agile_path(client, sprint.id)
119
+ '/jira/rest/agile/1.0/sprint//issue'
120
+ end
121
+ let(:issue) do
122
+ issue = double
123
+ allow(issue).to receive(:id).and_return(issue_id)
124
+ issue
125
+ end
126
+ let(:post_issue_input) do
127
+ { issues: [issue.id] }
128
+ end
129
+
130
+ describe '#add_issue' do
131
+ context 'when an issue is passed' do
132
+ it 'posts with the issue id' do
133
+ expect(client).to receive(:post).with(post_issue_path, post_issue_input.to_json)
134
+
135
+ expect(sprint.add_issue(issue)).to eq(issue)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ context 'when multiple issues exist' do
142
+ let(:issue_ids) { [1001, 1012] }
143
+ let(:post_issue_path) do
144
+ described_class.agile_path(client, sprint.id)
145
+ '/jira/rest/agile/1.0/sprint//issue'
146
+ end
147
+ let(:issues) do
148
+ issue_ids.map do |issue_id|
149
+ issue = double
150
+ allow(issue).to receive(:id).and_return(issue_id)
151
+ issue
152
+ end
153
+ end
154
+ let(:post_issue_input) do
155
+ { issues: issue_ids }
156
+ end
157
+
158
+ describe '#add_issues' do
159
+ context 'when an issue is passed' do
160
+ it 'posts with the issue id' do
161
+ expect(client).to receive(:post).with(post_issue_path, post_issue_input.to_json)
162
+
163
+ expect(sprint.add_issues(issues)).to eq(issues)
164
+ end
165
+ end
166
+ end
167
+ end
89
168
  end
90
169
  end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::Status do
4
+ let(:client) do
5
+ client = double(options: { rest_base_path: '/jira/rest/api/2' })
6
+ allow(client).to receive(:Field).and_return(JIRA::Resource::FieldFactory.new(client))
7
+ allow(client).to receive(:field_map_cache).and_return(nil)
8
+ client
9
+ end
10
+
11
+ describe '#status_category' do
12
+ subject do
13
+ described_class.new(client, attrs: JSON.parse(File.read('spec/mock_responses/status/1.json')))
14
+ end
15
+
16
+ it 'has a status_category relationship' do
17
+ expect(subject).to have_one(:status_category, JIRA::Resource::StatusCategory)
18
+ expect(subject.status_category.name).to eq('To Do')
19
+ end
20
+ end
21
+ end