jira-ruby 2.2.0 → 3.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +20 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/CI.yml +28 -0
- data/.github/workflows/codeql.yml +100 -0
- data/.github/workflows/rubocop.yml +18 -0
- data/.rubocop.yml +188 -0
- data/Gemfile +11 -3
- data/Guardfile +2 -0
- data/README.md +121 -20
- data/Rakefile +4 -5
- data/jira-ruby.gemspec +11 -17
- data/lib/jira/base.rb +37 -28
- data/lib/jira/base_factory.rb +4 -1
- data/lib/jira/client.rb +65 -46
- data/lib/jira/has_many_proxy.rb +4 -2
- data/lib/jira/http_client.rb +18 -13
- data/lib/jira/http_error.rb +4 -0
- data/lib/jira/jwt_client.rb +18 -42
- data/lib/jira/oauth_client.rb +6 -3
- data/lib/jira/railtie.rb +2 -0
- data/lib/jira/request_client.rb +5 -1
- data/lib/jira/resource/agile.rb +7 -9
- data/lib/jira/resource/applinks.rb +5 -3
- data/lib/jira/resource/attachment.rb +43 -3
- data/lib/jira/resource/board.rb +5 -3
- data/lib/jira/resource/board_configuration.rb +2 -0
- data/lib/jira/resource/comment.rb +2 -0
- data/lib/jira/resource/component.rb +2 -0
- data/lib/jira/resource/createmeta.rb +3 -1
- data/lib/jira/resource/field.rb +9 -4
- data/lib/jira/resource/filter.rb +2 -0
- data/lib/jira/resource/issue.rb +35 -44
- data/lib/jira/resource/issue_picker_suggestions.rb +4 -1
- data/lib/jira/resource/issue_picker_suggestions_issue.rb +2 -0
- data/lib/jira/resource/issuelink.rb +2 -0
- data/lib/jira/resource/issuelinktype.rb +2 -0
- data/lib/jira/resource/issuetype.rb +2 -0
- data/lib/jira/resource/priority.rb +2 -0
- data/lib/jira/resource/project.rb +4 -2
- data/lib/jira/resource/rapidview.rb +5 -3
- data/lib/jira/resource/remotelink.rb +2 -0
- data/lib/jira/resource/resolution.rb +2 -0
- data/lib/jira/resource/serverinfo.rb +2 -0
- data/lib/jira/resource/sprint.rb +14 -23
- data/lib/jira/resource/status.rb +7 -1
- data/lib/jira/resource/status_category.rb +10 -0
- data/lib/jira/resource/suggested_issue.rb +2 -0
- data/lib/jira/resource/transition.rb +2 -0
- data/lib/jira/resource/user.rb +3 -1
- data/lib/jira/resource/version.rb +2 -0
- data/lib/jira/resource/watcher.rb +2 -1
- data/lib/jira/resource/webhook.rb +4 -2
- data/lib/jira/resource/worklog.rb +3 -2
- data/lib/jira/version.rb +3 -1
- data/lib/jira-ruby.rb +5 -3
- data/lib/tasks/generate.rake +4 -2
- data/spec/data/files/short.txt +1 -0
- data/spec/integration/attachment_spec.rb +3 -3
- data/spec/integration/comment_spec.rb +8 -8
- data/spec/integration/component_spec.rb +7 -7
- data/spec/integration/field_spec.rb +3 -3
- data/spec/integration/issue_spec.rb +20 -16
- data/spec/integration/issuelinktype_spec.rb +3 -3
- data/spec/integration/issuetype_spec.rb +3 -3
- data/spec/integration/priority_spec.rb +3 -3
- data/spec/integration/project_spec.rb +7 -7
- data/spec/integration/rapidview_spec.rb +9 -9
- data/spec/integration/resolution_spec.rb +3 -3
- data/spec/integration/status_category_spec.rb +20 -0
- data/spec/integration/status_spec.rb +4 -8
- data/spec/integration/transition_spec.rb +2 -2
- data/spec/integration/user_spec.rb +22 -8
- data/spec/integration/version_spec.rb +7 -7
- data/spec/integration/watcher_spec.rb +17 -18
- data/spec/integration/webhook.rb +5 -4
- data/spec/integration/worklog_spec.rb +8 -8
- data/spec/jira/base_factory_spec.rb +2 -1
- data/spec/jira/base_spec.rb +55 -41
- data/spec/jira/client_spec.rb +48 -34
- data/spec/jira/has_many_proxy_spec.rb +3 -3
- data/spec/jira/http_client_spec.rb +98 -26
- data/spec/jira/http_error_spec.rb +2 -2
- data/spec/jira/oauth_client_spec.rb +30 -8
- data/spec/jira/request_client_spec.rb +4 -4
- data/spec/jira/resource/agile_spec.rb +28 -28
- data/spec/jira/resource/attachment_spec.rb +142 -52
- data/spec/jira/resource/board_spec.rb +21 -20
- data/spec/jira/resource/createmeta_spec.rb +48 -48
- data/spec/jira/resource/field_spec.rb +30 -12
- data/spec/jira/resource/filter_spec.rb +4 -4
- data/spec/jira/resource/issue_picker_suggestions_spec.rb +18 -18
- data/spec/jira/resource/issue_spec.rb +44 -38
- data/spec/jira/resource/jira_picker_suggestions_issue_spec.rb +3 -3
- data/spec/jira/resource/project_factory_spec.rb +3 -2
- data/spec/jira/resource/project_spec.rb +16 -16
- data/spec/jira/resource/sprint_spec.rb +70 -3
- data/spec/jira/resource/status_spec.rb +21 -0
- data/spec/jira/resource/user_factory_spec.rb +4 -4
- data/spec/jira/resource/worklog_spec.rb +3 -3
- data/spec/mock_responses/sprint/1.json +13 -0
- data/spec/mock_responses/status/1.json +8 -1
- data/spec/mock_responses/status.json +40 -5
- data/spec/mock_responses/statuscategory/1.json +7 -0
- data/spec/mock_responses/statuscategory.json +30 -0
- data/spec/mock_responses/{user_username=admin.json → user_accountId=1234567890abcdef01234567.json} +2 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/support/clients_helper.rb +3 -5
- data/spec/support/shared_examples/integration.rb +25 -28
- metadata +25 -257
- data/.travis.yml +0 -9
- data/example.rb +0 -232
- data/http-basic-example.rb +0 -113
- data/lib/jira/resource/sprint_report.rb +0 -8
- data/lib/jira/tasks.rb +0 -0
- data/spec/jira/jwt_uri_builder_spec.rb +0 -59
@@ -5,7 +5,7 @@ describe JIRA::Resource::Watcher do
|
|
5
5
|
let(:client) { client }
|
6
6
|
let(:site_url) { site_url }
|
7
7
|
|
8
|
-
let(:target) {
|
8
|
+
let(:target) { described_class.new(client, attrs: { 'id' => '99999' }, issue_id: '10002') }
|
9
9
|
|
10
10
|
let(:belongs_to) do
|
11
11
|
JIRA::Resource::Issue.new(client, attrs: {
|
@@ -19,44 +19,43 @@ describe JIRA::Resource::Watcher do
|
|
19
19
|
let(:expected_attributes) do
|
20
20
|
{
|
21
21
|
'self' => 'http://localhost:2990/jira/rest/api/2/issue/10002/watchers',
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
isWatching: false,
|
23
|
+
watchCount: 1,
|
24
|
+
watchers: [
|
25
25
|
{
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
self: 'http://www.example.com/jira/rest/api/2/user?username=admin',
|
27
|
+
name: 'admin',
|
28
|
+
displayName: 'admin',
|
29
|
+
active: false
|
30
30
|
}
|
31
31
|
]
|
32
32
|
}
|
33
33
|
end
|
34
34
|
|
35
35
|
describe 'watchers' do
|
36
|
-
before
|
37
|
-
stub_request(:get, site_url
|
36
|
+
before do
|
37
|
+
stub_request(:get, "#{site_url}/jira/rest/api/2/issue/10002")
|
38
38
|
.to_return(status: 200, body: get_mock_response('issue/10002.json'))
|
39
39
|
|
40
|
-
stub_request(:get, site_url
|
40
|
+
stub_request(:get, "#{site_url}/jira/rest/api/2/issue/10002/watchers")
|
41
41
|
.to_return(status: 200, body: get_mock_response('issue/10002/watchers.json'))
|
42
42
|
|
43
|
-
stub_request(:post, site_url
|
43
|
+
stub_request(:post, "#{site_url}/jira/rest/api/2/issue/10002/watchers")
|
44
44
|
.to_return(status: 204, body: nil)
|
45
45
|
end
|
46
46
|
|
47
|
-
it '
|
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(options = { issue: })
|
50
50
|
expect(watchers.length).to eq(1)
|
51
51
|
end
|
52
52
|
|
53
|
-
it '
|
53
|
+
it 'adds a watcher' do
|
54
54
|
issue = client.Issue.find('10002')
|
55
|
-
watcher =
|
56
|
-
user_id =
|
55
|
+
watcher = described_class.new(client, issue:)
|
56
|
+
user_id = 'tester'
|
57
57
|
watcher.save!(user_id)
|
58
58
|
end
|
59
59
|
end
|
60
|
-
|
61
60
|
end
|
62
61
|
end
|
data/spec/integration/webhook.rb
CHANGED
@@ -8,14 +8,15 @@ describe JIRA::Resource::Webhook do
|
|
8
8
|
let(:key) { '2' }
|
9
9
|
|
10
10
|
let(:expected_attributes) do
|
11
|
-
{ 'name' => 'from API', 'url' => 'http://localhost:3000/webhooks/1', 'excludeBody' => false,
|
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
13
|
end
|
13
14
|
|
14
15
|
let(:expected_collection_length) { 1 }
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
it_behaves_like 'a resource'
|
18
|
+
it_behaves_like 'a resource with a collection GET endpoint'
|
19
|
+
it_behaves_like 'a resource with a singular GET endpoint'
|
19
20
|
|
20
21
|
it 'returns a collection of components' do
|
21
22
|
stub_request(:get, site_url + described_class.singular_path(client, key))
|
@@ -7,7 +7,7 @@ describe JIRA::Resource::Worklog do
|
|
7
7
|
|
8
8
|
let(:key) { '10000' }
|
9
9
|
|
10
|
-
let(:target) {
|
10
|
+
let(:target) { described_class.new(client, attrs: { 'id' => '99999' }, issue_id: '54321') }
|
11
11
|
|
12
12
|
let(:expected_collection_length) { 3 }
|
13
13
|
|
@@ -22,7 +22,7 @@ describe JIRA::Resource::Worklog do
|
|
22
22
|
let(:expected_attributes) do
|
23
23
|
{
|
24
24
|
'self' => 'http://localhost:2990/jira/rest/api/2/issue/10002/worklog/10000',
|
25
|
-
'id'
|
25
|
+
'id' => key,
|
26
26
|
'comment' => 'Some epic work.'
|
27
27
|
}
|
28
28
|
end
|
@@ -41,11 +41,11 @@ describe JIRA::Resource::Worklog do
|
|
41
41
|
{ 'id' => '10001', 'timeSpent' => '4d' }
|
42
42
|
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
it_behaves_like 'a resource'
|
45
|
+
it_behaves_like 'a resource with a collection GET endpoint'
|
46
|
+
it_behaves_like 'a resource with a singular GET endpoint'
|
47
|
+
it_behaves_like 'a resource with a DELETE endpoint'
|
48
|
+
it_behaves_like 'a resource with a POST endpoint'
|
49
|
+
it_behaves_like 'a resource with a PUT endpoint'
|
50
50
|
end
|
51
51
|
end
|
@@ -4,9 +4,10 @@ describe JIRA::BaseFactory do
|
|
4
4
|
class JIRA::Resource::FooFactory < JIRA::BaseFactory; end
|
5
5
|
class JIRA::Resource::Foo; end
|
6
6
|
|
7
|
-
let(:client) { double }
|
8
7
|
subject { JIRA::Resource::FooFactory.new(client) }
|
9
8
|
|
9
|
+
let(:client) { double }
|
10
|
+
|
10
11
|
it 'initializes correctly' do
|
11
12
|
expect(subject.class).to eq(JIRA::Resource::FooFactory)
|
12
13
|
expect(subject.client).to eq(client)
|
data/spec/jira/base_spec.rb
CHANGED
@@ -32,11 +32,11 @@ describe JIRA::Base do
|
|
32
32
|
attribute_key: 'irregularlyNamedThings'
|
33
33
|
end
|
34
34
|
|
35
|
+
subject { JIRA::Resource::Deadbeef.new(client, attrs:) }
|
36
|
+
|
35
37
|
let(:client) { double('client') }
|
36
38
|
let(:attrs) { {} }
|
37
39
|
|
38
|
-
subject { JIRA::Resource::Deadbeef.new(client, attrs: attrs) }
|
39
|
-
|
40
40
|
let(:decorated) { JIRADelegation.new(subject) }
|
41
41
|
|
42
42
|
describe '#respond_to?' do
|
@@ -44,6 +44,7 @@ describe JIRA::Base do
|
|
44
44
|
it 'responds to client' do
|
45
45
|
expect(decorated.respond_to?(:client)).to eq(true)
|
46
46
|
end
|
47
|
+
|
47
48
|
it 'does not raise an error' do
|
48
49
|
expect do
|
49
50
|
decorated.respond_to?(:client)
|
@@ -125,7 +126,7 @@ describe JIRA::Base do
|
|
125
126
|
end
|
126
127
|
|
127
128
|
describe 'collection_path' do
|
128
|
-
before
|
129
|
+
before do
|
129
130
|
expect(client).to receive(:options).and_return(rest_base_path: '/deadbeef/bar')
|
130
131
|
end
|
131
132
|
|
@@ -147,8 +148,9 @@ describe JIRA::Base do
|
|
147
148
|
end
|
148
149
|
|
149
150
|
describe 'dynamic instance methods' do
|
151
|
+
subject { JIRA::Resource::Deadbeef.new(client, attrs:) }
|
152
|
+
|
150
153
|
let(:attrs) { { 'foo' => 'bar', 'flum' => 'goo', 'object_id' => 'dummy' } }
|
151
|
-
subject { JIRA::Resource::Deadbeef.new(client, attrs: attrs) }
|
152
154
|
|
153
155
|
it 'responds to each of the top level attribute names' do
|
154
156
|
expect(subject).to respond_to(:foo)
|
@@ -169,7 +171,7 @@ describe JIRA::Base do
|
|
169
171
|
subject { JIRA::Resource::Deadbeef.new(client, attrs: { 'id' => '98765' }) }
|
170
172
|
|
171
173
|
describe 'not cached' do
|
172
|
-
before
|
174
|
+
before do
|
173
175
|
response = instance_double('Response', body: '{"self":"http://deadbeef/","id":"98765"}')
|
174
176
|
expect(client).to receive(:get).with('/jira/rest/api/2/deadbeef/98765').and_return(response)
|
175
177
|
expect(JIRA::Resource::Deadbeef).to receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
|
@@ -222,17 +224,17 @@ describe JIRA::Base do
|
|
222
224
|
end
|
223
225
|
|
224
226
|
describe 'save' do
|
225
|
-
let(:response) { double }
|
226
|
-
|
227
227
|
subject { JIRA::Resource::Deadbeef.new(client) }
|
228
228
|
|
229
|
-
|
229
|
+
let(:response) { double }
|
230
|
+
|
231
|
+
before do
|
230
232
|
expect(subject).to receive(:url).and_return('/foo/bar')
|
231
233
|
end
|
232
234
|
|
233
235
|
it 'POSTs a new record' do
|
234
236
|
response = instance_double('Response', body: '{"id":"123"}')
|
235
|
-
allow(subject).to receive(:new_record?)
|
237
|
+
allow(subject).to receive(:new_record?).and_return(true)
|
236
238
|
expect(client).to receive(:post).with('/foo/bar', '{"foo":"bar"}').and_return(response)
|
237
239
|
expect(subject.save('foo' => 'bar')).to be_truthy
|
238
240
|
expect(subject.id).to eq('123')
|
@@ -241,7 +243,7 @@ describe JIRA::Base do
|
|
241
243
|
|
242
244
|
it 'PUTs an existing record' do
|
243
245
|
response = instance_double('Response', body: nil)
|
244
|
-
allow(subject).to receive(:new_record?)
|
246
|
+
allow(subject).to receive(:new_record?).and_return(false)
|
245
247
|
expect(client).to receive(:put).with('/foo/bar', '{"foo":"bar"}').and_return(response)
|
246
248
|
expect(subject.save('foo' => 'bar')).to be_truthy
|
247
249
|
expect(subject.expanded).to be_falsey
|
@@ -255,14 +257,17 @@ describe JIRA::Base do
|
|
255
257
|
expect(subject.foo).to eq('bar' => 'baz', 'fum' => 'dum')
|
256
258
|
end
|
257
259
|
|
258
|
-
it 'returns false when an invalid field is set' do
|
260
|
+
it 'returns false when an invalid field is set' do
|
261
|
+
# The JIRA REST API apparently ignores fields that you aren't allowed to set manually
|
259
262
|
response = instance_double('Response', body: '{"errorMessages":["blah"]}', status: 400)
|
260
|
-
allow(subject).to receive(:new_record?)
|
261
|
-
expect(client).to receive(:put).with('/foo/bar',
|
263
|
+
allow(subject).to receive(:new_record?).and_return(false)
|
264
|
+
expect(client).to receive(:put).with('/foo/bar',
|
265
|
+
'{"invalid_field":"foobar"}').and_raise(JIRA::HTTPError.new(response))
|
262
266
|
expect(subject.save('invalid_field' => 'foobar')).to be_falsey
|
263
267
|
end
|
264
268
|
|
265
|
-
it 'returns false with exception details when non json response body (unauthorized)' do
|
269
|
+
it 'returns false with exception details when non json response body (unauthorized)' do
|
270
|
+
# Unauthorized requests return a non-json body. This makes sure we can handle non-json bodies on HTTPError
|
266
271
|
response = double('Response', body: 'totally invalid json', code: 401, message: 'Unauthorized')
|
267
272
|
expect(client).to receive(:post).with('/foo/bar', '{"foo":"bar"}').and_raise(JIRA::HTTPError.new(response))
|
268
273
|
expect(subject.save('foo' => 'bar')).to be_falsey
|
@@ -272,17 +277,17 @@ describe JIRA::Base do
|
|
272
277
|
end
|
273
278
|
|
274
279
|
describe 'save!' do
|
275
|
-
let(:response) { double }
|
276
|
-
|
277
280
|
subject { JIRA::Resource::Deadbeef.new(client) }
|
278
281
|
|
279
|
-
|
282
|
+
let(:response) { double }
|
283
|
+
|
284
|
+
before do
|
280
285
|
expect(subject).to receive(:url).and_return('/foo/bar')
|
281
286
|
end
|
282
287
|
|
283
288
|
it 'POSTs a new record' do
|
284
289
|
response = instance_double('Response', body: '{"id":"123"}')
|
285
|
-
allow(subject).to receive(:new_record?)
|
290
|
+
allow(subject).to receive(:new_record?).and_return(true)
|
286
291
|
expect(client).to receive(:post).with('/foo/bar', '{"foo":"bar"}').and_return(response)
|
287
292
|
expect(subject.save!('foo' => 'bar')).to be_truthy
|
288
293
|
expect(subject.id).to eq('123')
|
@@ -291,7 +296,7 @@ describe JIRA::Base do
|
|
291
296
|
|
292
297
|
it 'PUTs an existing record' do
|
293
298
|
response = instance_double('Response', body: nil)
|
294
|
-
allow(subject).to receive(:new_record?)
|
299
|
+
allow(subject).to receive(:new_record?).and_return(false)
|
295
300
|
expect(client).to receive(:put).with('/foo/bar', '{"foo":"bar"}').and_return(response)
|
296
301
|
expect(subject.save!('foo' => 'bar')).to be_truthy
|
297
302
|
expect(subject.expanded).to be_falsey
|
@@ -299,9 +304,10 @@ describe JIRA::Base do
|
|
299
304
|
|
300
305
|
it 'throws an exception when an invalid field is set' do
|
301
306
|
response = instance_double('Response', body: '{"errorMessages":["blah"]}', status: 400)
|
302
|
-
allow(subject).to receive(:new_record?)
|
303
|
-
expect(client).to receive(:put).with('/foo/bar',
|
304
|
-
|
307
|
+
allow(subject).to receive(:new_record?).and_return(false)
|
308
|
+
expect(client).to receive(:put).with('/foo/bar',
|
309
|
+
'{"invalid_field":"foobar"}').and_raise(JIRA::HTTPError.new(response))
|
310
|
+
expect { subject.save!('invalid_field' => 'foobar') }.to raise_error(JIRA::HTTPError)
|
305
311
|
end
|
306
312
|
end
|
307
313
|
|
@@ -320,9 +326,9 @@ describe JIRA::Base do
|
|
320
326
|
end
|
321
327
|
|
322
328
|
describe 'delete' do
|
323
|
-
before
|
329
|
+
before do
|
324
330
|
expect(client).to receive(:delete).with('/foo/bar')
|
325
|
-
allow(subject).to receive(:url)
|
331
|
+
allow(subject).to receive(:url).and_return('/foo/bar')
|
326
332
|
end
|
327
333
|
|
328
334
|
it 'flags itself as deleted' do
|
@@ -360,8 +366,8 @@ describe JIRA::Base do
|
|
360
366
|
end
|
361
367
|
|
362
368
|
describe 'url' do
|
363
|
-
before
|
364
|
-
allow(client).to receive(:options)
|
369
|
+
before do
|
370
|
+
allow(client).to receive(:options).and_return({ rest_base_path: '/foo/bar' })
|
365
371
|
end
|
366
372
|
|
367
373
|
it 'returns self as the URL if set' do
|
@@ -370,13 +376,13 @@ describe JIRA::Base do
|
|
370
376
|
end
|
371
377
|
|
372
378
|
it 'returns path as the URL if set and site options is specified' do
|
373
|
-
allow(client).to receive(:options)
|
379
|
+
allow(client).to receive(:options).and_return({ site: 'http://foo' })
|
374
380
|
attrs['self'] = 'http://foo/bar'
|
375
381
|
expect(subject.url).to eq('/bar')
|
376
382
|
end
|
377
383
|
|
378
384
|
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)
|
385
|
+
allow(client).to receive(:options).and_return({ site: 'http://foo/' })
|
380
386
|
attrs['self'] = 'http://foo/bar'
|
381
387
|
expect(subject.url).to eq('/bar')
|
382
388
|
end
|
@@ -428,7 +434,7 @@ describe JIRA::Base do
|
|
428
434
|
|
429
435
|
h = { 'key' => subject }
|
430
436
|
h_attrs = { 'key' => subject.attrs }
|
431
|
-
expect(h.to_json).to eq(h_attrs.to_json)
|
437
|
+
expect(h['key'].to_json).to eq(h_attrs['key'].to_json)
|
432
438
|
end
|
433
439
|
|
434
440
|
describe 'extract attrs from response' do
|
@@ -487,7 +493,9 @@ describe JIRA::Base do
|
|
487
493
|
end
|
488
494
|
|
489
495
|
it 'allows the has_many attributes to be nested inside another attribute' do
|
490
|
-
subject = JIRA::Resource::HasManyExample.new(client,
|
496
|
+
subject = JIRA::Resource::HasManyExample.new(client,
|
497
|
+
attrs: { 'nested' => { 'brunchmuffins' => [{ 'id' => '123' },
|
498
|
+
{ 'id' => '456' }] } })
|
491
499
|
expect(subject.brunchmuffins.length).to eq(2)
|
492
500
|
subject.brunchmuffins.each do |brunchmuffin|
|
493
501
|
expect(brunchmuffin.class).to eq(JIRA::Resource::Deadbeef)
|
@@ -495,9 +503,12 @@ describe JIRA::Base do
|
|
495
503
|
end
|
496
504
|
|
497
505
|
it 'allows it to be deeply nested' do
|
498
|
-
subject = JIRA::Resource::HasManyExample.new(
|
499
|
-
|
500
|
-
|
506
|
+
subject = JIRA::Resource::HasManyExample.new(
|
507
|
+
client,
|
508
|
+
attrs: {
|
509
|
+
'nested' => { 'breakfastscone' => { 'breakfastscones' => [{ 'id' => '123' }, { 'id' => '456' }] } }
|
510
|
+
}
|
511
|
+
)
|
501
512
|
expect(subject.breakfastscones.length).to eq(2)
|
502
513
|
subject.breakfastscones.each do |breakfastscone|
|
503
514
|
expect(breakfastscone.class).to eq(JIRA::Resource::Deadbeef)
|
@@ -512,7 +523,9 @@ describe JIRA::Base do
|
|
512
523
|
end
|
513
524
|
|
514
525
|
it 'allows the attribute key to be specified' do
|
515
|
-
subject = JIRA::Resource::HasManyExample.new(client,
|
526
|
+
subject = JIRA::Resource::HasManyExample.new(client,
|
527
|
+
attrs: { 'irregularlyNamedThings' => [{ 'id' => '123' },
|
528
|
+
{ 'id' => '456' }] })
|
516
529
|
expect(subject.irregularly_named_things.length).to eq(2)
|
517
530
|
subject.irregularly_named_things.each do |thing|
|
518
531
|
expect(thing.class).to eq(JIRA::Resource::Deadbeef)
|
@@ -545,7 +558,8 @@ describe JIRA::Base do
|
|
545
558
|
end
|
546
559
|
|
547
560
|
it 'allows the has_one attributes to be nested inside another attribute' do
|
548
|
-
subject = JIRA::Resource::HasOneExample.new(client,
|
561
|
+
subject = JIRA::Resource::HasOneExample.new(client,
|
562
|
+
attrs: { 'nested' => { 'brunchmuffin' => { 'id' => '123' } } })
|
549
563
|
expect(subject.brunchmuffin.class).to eq(JIRA::Resource::Deadbeef)
|
550
564
|
expect(subject.brunchmuffin.id).to eq('123')
|
551
565
|
end
|
@@ -570,27 +584,27 @@ describe JIRA::Base do
|
|
570
584
|
belongs_to :deadbeef
|
571
585
|
end
|
572
586
|
|
573
|
-
|
587
|
+
subject { JIRA::Resource::BelongsToExample.new(client, attrs: { 'id' => '123' }, deadbeef:) }
|
574
588
|
|
575
|
-
|
589
|
+
let(:deadbeef) { JIRA::Resource::Deadbeef.new(client, attrs: { 'id' => '999' }) }
|
576
590
|
|
577
591
|
it 'sets up an accessor for the belongs to relationship' do
|
578
592
|
expect(subject.deadbeef).to eq(deadbeef)
|
579
593
|
end
|
580
594
|
|
581
595
|
it 'raises an exception when initialized without a belongs_to instance' do
|
582
|
-
expect
|
596
|
+
expect do
|
583
597
|
JIRA::Resource::BelongsToExample.new(client, attrs: { 'id' => '123' })
|
584
|
-
|
598
|
+
end.to raise_exception(ArgumentError, 'Required option :deadbeef missing')
|
585
599
|
end
|
586
600
|
|
587
601
|
it 'returns the right url' do
|
588
|
-
allow(client).to receive(:options)
|
602
|
+
allow(client).to receive(:options).and_return({ rest_base_path: '/foo' })
|
589
603
|
expect(subject.url).to eq('/foo/deadbeef/999/belongstoexample/123')
|
590
604
|
end
|
591
605
|
|
592
606
|
it 'can be initialized with an instance or a key value' do
|
593
|
-
allow(client).to receive(:options)
|
607
|
+
allow(client).to receive(:options).and_return({ rest_base_path: '/foo' })
|
594
608
|
subject = JIRA::Resource::BelongsToExample.new(client, attrs: { 'id' => '123' }, deadbeef_id: '987')
|
595
609
|
expect(subject.url).to eq('/foo/deadbeef/987/belongstoexample/123')
|
596
610
|
end
|
data/spec/jira/client_spec.rb
CHANGED
@@ -28,7 +28,7 @@ RSpec.shared_examples 'Client Common Tests' do
|
|
28
28
|
expect(subject).to receive(:merge_default_headers).exactly(3).times.with({})
|
29
29
|
|
30
30
|
# response for merging headers for http methods with body
|
31
|
-
expect(subject).to receive(:merge_default_headers).
|
31
|
+
expect(subject).to receive(:merge_default_headers).twice.with(content_type_header)
|
32
32
|
|
33
33
|
%i[delete get head].each { |method| subject.send(method, '/path', {}) }
|
34
34
|
%i[post put].each { |method| subject.send(method, '/path', '', content_type_header) }
|
@@ -62,13 +62,20 @@ RSpec.shared_examples 'Client Common Tests' do
|
|
62
62
|
|
63
63
|
describe 'SSL client options' do
|
64
64
|
context 'without certificate and key' do
|
65
|
-
let(:options) { { use_client_cert: true } }
|
66
65
|
subject { JIRA::Client.new(options) }
|
67
66
|
|
67
|
+
let(:options) { { use_client_cert: true } }
|
68
|
+
|
68
69
|
it 'raises an ArgumentError' do
|
69
|
-
expect
|
70
|
+
expect do
|
71
|
+
subject
|
72
|
+
end.to raise_exception(ArgumentError,
|
73
|
+
'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true')
|
70
74
|
options[:ssl_client_cert] = '<cert></cert>'
|
71
|
-
expect
|
75
|
+
expect do
|
76
|
+
subject
|
77
|
+
end.to raise_exception(ArgumentError,
|
78
|
+
'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true')
|
72
79
|
end
|
73
80
|
end
|
74
81
|
end
|
@@ -77,11 +84,13 @@ end
|
|
77
84
|
RSpec.shared_examples 'HttpClient tests' do
|
78
85
|
it 'makes a valid request' do
|
79
86
|
%i[delete get head].each do |method|
|
80
|
-
expect(subject.request_client).to receive(:make_request).with(method, '/path', nil,
|
87
|
+
expect(subject.request_client).to receive(:make_request).with(method, '/path', nil,
|
88
|
+
headers).and_return(successful_response)
|
81
89
|
subject.send(method, '/path', headers)
|
82
90
|
end
|
83
91
|
%i[post put].each do |method|
|
84
|
-
expect(subject.request_client).to receive(:make_request).with(method, '/path', '',
|
92
|
+
expect(subject.request_client).to receive(:make_request).with(method, '/path', '',
|
93
|
+
merged_headers).and_return(successful_response)
|
85
94
|
subject.send(method, '/path', '', headers)
|
86
95
|
end
|
87
96
|
end
|
@@ -106,11 +115,13 @@ RSpec.shared_examples 'OAuth Common Tests' do
|
|
106
115
|
describe 'that call a oauth client' do
|
107
116
|
specify 'which makes a request' do
|
108
117
|
%i[delete get head].each do |method|
|
109
|
-
expect(subject.request_client).to receive(:make_request).with(method, '/path', nil,
|
118
|
+
expect(subject.request_client).to receive(:make_request).with(method, '/path', nil,
|
119
|
+
headers).and_return(successful_response)
|
110
120
|
subject.send(method, '/path', {})
|
111
121
|
end
|
112
122
|
%i[post put].each do |method|
|
113
|
-
expect(subject.request_client).to receive(:make_request).with(method, '/path', '',
|
123
|
+
expect(subject.request_client).to receive(:make_request).with(method, '/path', '',
|
124
|
+
merged_headers).and_return(successful_response)
|
114
125
|
subject.send(method, '/path', '', {})
|
115
126
|
end
|
116
127
|
end
|
@@ -130,20 +141,22 @@ describe JIRA::Client do
|
|
130
141
|
|
131
142
|
context 'behaviour that applies to all client classes irrespective of authentication method' do
|
132
143
|
it 'allows the overriding of some options' do
|
133
|
-
client =
|
144
|
+
client = described_class.new(consumer_key: 'foo', consumer_secret: 'bar', site: 'http://foo.com/')
|
134
145
|
expect(client.options[:site]).to eq('http://foo.com/')
|
135
146
|
expect(JIRA::Client::DEFAULT_OPTIONS[:site]).not_to eq('http://foo.com/')
|
136
147
|
end
|
137
148
|
end
|
138
149
|
|
139
150
|
context 'with basic http authentication' do
|
140
|
-
subject {
|
151
|
+
subject { described_class.new(username: 'foo', password: 'bar', auth_type: :basic) }
|
141
152
|
|
142
|
-
before
|
143
|
-
stub_request(:get, 'https://
|
153
|
+
before do
|
154
|
+
stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
|
155
|
+
.with(headers: { 'Authorization' => "Basic #{Base64.strict_encode64('foo:bar').chomp}" })
|
144
156
|
.to_return(status: 200, body: '[]', headers: {})
|
145
157
|
|
146
|
-
stub_request(:get, 'https://
|
158
|
+
stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
|
159
|
+
.with(headers: { 'Authorization' => "Basic #{Base64.strict_encode64('foo:badpassword').chomp}" })
|
147
160
|
.to_return(status: 401, headers: {})
|
148
161
|
end
|
149
162
|
|
@@ -157,33 +170,33 @@ describe JIRA::Client do
|
|
157
170
|
expect(subject.options[:password]).to eq('bar')
|
158
171
|
end
|
159
172
|
|
160
|
-
it 'fails with wrong user name and password' do
|
161
|
-
bad_login = JIRA::Client.new(username: 'foo', password: 'badpassword', auth_type: :basic)
|
162
|
-
expect(bad_login.authenticated?).to be_falsey
|
163
|
-
expect { bad_login.Project.all }.to raise_error JIRA::HTTPError
|
164
|
-
end
|
165
|
-
|
166
173
|
it 'only returns a true for #authenticated? once we have requested some data' do
|
167
|
-
expect(subject.authenticated?).to
|
174
|
+
expect(subject.authenticated?).to be_nil
|
168
175
|
expect(subject.Project.all).to be_empty
|
169
176
|
expect(subject.authenticated?).to be_truthy
|
170
177
|
end
|
178
|
+
|
179
|
+
it 'fails with wrong user name and password' do
|
180
|
+
bad_login = described_class.new(username: 'foo', password: 'badpassword', auth_type: :basic)
|
181
|
+
expect(bad_login.authenticated?).to be_falsey
|
182
|
+
expect { bad_login.Project.all }.to raise_error JIRA::HTTPError
|
183
|
+
end
|
171
184
|
end
|
172
185
|
|
173
186
|
context 'with cookie authentication' do
|
174
|
-
subject {
|
187
|
+
subject { described_class.new(username: 'foo', password: 'bar', auth_type: :cookie) }
|
175
188
|
|
176
189
|
let(:session_cookie) { '6E3487971234567896704A9EB4AE501F' }
|
177
190
|
let(:session_body) do
|
178
191
|
{
|
179
|
-
|
180
|
-
|
192
|
+
session: { 'name' => 'JSESSIONID', 'value' => session_cookie },
|
193
|
+
loginInfo: { 'failedLoginCount' => 1, 'loginCount' => 2,
|
181
194
|
'lastFailedLoginTime' => (DateTime.now - 2).iso8601,
|
182
195
|
'previousLoginTime' => (DateTime.now - 5).iso8601 }
|
183
196
|
}
|
184
197
|
end
|
185
198
|
|
186
|
-
before
|
199
|
+
before do
|
187
200
|
# General case of API call with no authentication, or wrong authentication
|
188
201
|
stub_request(:post, 'https://localhost:2990/jira/rest/auth/1/session')
|
189
202
|
.to_return(status: 401, headers: {})
|
@@ -210,7 +223,7 @@ describe JIRA::Client do
|
|
210
223
|
end
|
211
224
|
|
212
225
|
it 'does not authenticate with an incorrect username and password' do
|
213
|
-
bad_client =
|
226
|
+
bad_client = described_class.new(username: 'foo', password: 'bad_password', auth_type: :cookie)
|
214
227
|
expect(bad_client).not_to be_authenticated
|
215
228
|
end
|
216
229
|
|
@@ -222,7 +235,7 @@ describe JIRA::Client do
|
|
222
235
|
|
223
236
|
context 'with jwt authentication' do
|
224
237
|
subject do
|
225
|
-
|
238
|
+
described_class.new(
|
226
239
|
issuer: 'foo',
|
227
240
|
base_url: 'https://host.tld',
|
228
241
|
shared_secret: 'shared_secret_key',
|
@@ -230,10 +243,10 @@ describe JIRA::Client do
|
|
230
243
|
)
|
231
244
|
end
|
232
245
|
|
233
|
-
before
|
246
|
+
before do
|
234
247
|
stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
|
235
|
-
|
236
|
-
|
248
|
+
.with(headers: { 'Authorization' => /JWT .+/ })
|
249
|
+
.to_return(status: 200, body: '[]', headers: {})
|
237
250
|
end
|
238
251
|
|
239
252
|
include_examples 'Client Common Tests'
|
@@ -248,8 +261,8 @@ describe JIRA::Client do
|
|
248
261
|
context 'with a incorrect jwt key' do
|
249
262
|
before do
|
250
263
|
stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
|
251
|
-
|
252
|
-
|
264
|
+
.with(headers: { 'Authorization' => /JWT .+/ })
|
265
|
+
.to_return(status: 401, body: '[]', headers: {})
|
253
266
|
end
|
254
267
|
|
255
268
|
it 'is not authenticated' do
|
@@ -269,20 +282,21 @@ describe JIRA::Client do
|
|
269
282
|
end
|
270
283
|
|
271
284
|
context 'oauth authentication' do
|
272
|
-
subject {
|
285
|
+
subject { described_class.new(consumer_key: 'foo', consumer_secret: 'bar') }
|
273
286
|
|
274
287
|
include_examples 'OAuth Common Tests'
|
275
288
|
end
|
276
289
|
|
277
290
|
context 'with oauth_2legged' do
|
278
|
-
subject {
|
291
|
+
subject { described_class.new(consumer_key: 'foo', consumer_secret: 'bar', auth_type: :oauth_2legged) }
|
279
292
|
|
280
293
|
include_examples 'OAuth Common Tests'
|
281
294
|
end
|
282
295
|
|
283
296
|
context 'with unknown options' do
|
297
|
+
subject { described_class.new(options) }
|
298
|
+
|
284
299
|
let(:options) { { 'username' => 'foo', 'password' => 'bar', auth_type: :basic } }
|
285
|
-
subject { JIRA::Client.new(options) }
|
286
300
|
|
287
301
|
it 'raises an ArgumentError' do
|
288
302
|
expect { subject }.to raise_exception(ArgumentError, 'Unknown option(s) given: ["username", "password"]')
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
describe JIRA::HasManyProxy do
|
4
4
|
class Foo; end
|
5
5
|
|
6
|
-
subject {
|
6
|
+
subject { described_class.new(parent, Foo, collection) }
|
7
7
|
|
8
8
|
let(:parent) { double('parent') }
|
9
9
|
let(:collection) { double('collection') }
|
@@ -25,7 +25,7 @@ describe JIRA::HasManyProxy do
|
|
25
25
|
foo = double('foo')
|
26
26
|
allow(parent).to receive(:client).and_return(client)
|
27
27
|
allow(parent).to receive(:to_sym).and_return(:parent)
|
28
|
-
expect(Foo).to receive(:new).with(client, attrs: { 'foo' => 'bar' }, parent:
|
28
|
+
expect(Foo).to receive(:new).with(client, attrs: { 'foo' => 'bar' }, parent:).and_return(foo)
|
29
29
|
expect(collection).to receive(:<<).with(foo)
|
30
30
|
expect(subject.build('foo' => 'bar')).to eq(foo)
|
31
31
|
end
|
@@ -35,7 +35,7 @@ describe JIRA::HasManyProxy do
|
|
35
35
|
client = double('client')
|
36
36
|
allow(parent).to receive(:client).and_return(client)
|
37
37
|
allow(parent).to receive(:to_sym).and_return(:parent)
|
38
|
-
expect(Foo).to receive(:all).with(client, parent:
|
38
|
+
expect(Foo).to receive(:all).with(client, parent:).and_return(foo)
|
39
39
|
expect(subject.all).to eq(foo)
|
40
40
|
end
|
41
41
|
|