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
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::Properties do
4
+ with_each_client do |site_url, client|
5
+ context 'when accessing singular resource' do
6
+ let(:client) { client }
7
+ let(:site_url) { site_url }
8
+ let(:key) { 'xyz' }
9
+ let(:target) { described_class.new(client, attrs: { 'key' => 'xyz' }, issue_id: '10002') }
10
+ let(:belongs_to) { JIRA::Resource::Issue.new(client, attrs: { 'id' => '10002' }) }
11
+ let(:expected_attributes) { { 'key' => key, 'value' => 'supercalifragilistic' } }
12
+ let(:attributes_for_put) { { 'value' => 'expialidocious' } }
13
+ let(:expected_attributes_from_put) { { 'key' => key, 'value' => 'expialidocious' } }
14
+
15
+ it_behaves_like 'a resource'
16
+ it_behaves_like 'a resource with a singular GET endpoint'
17
+ it_behaves_like 'a resource with a DELETE endpoint'
18
+ it_behaves_like 'a resource with a PUT endpoint'
19
+ end
20
+
21
+ context 'when accessing collection' do
22
+ let(:client) { client }
23
+ let(:site_url) { site_url }
24
+ let(:key) { 'xyz' }
25
+ let(:belongs_to) { JIRA::Resource::Issue.new(client, attrs: { 'id' => '10002' }) }
26
+ let(:expected_collection_length) { 2 }
27
+ let(:expected_attributes) { { 'key' => key, 'value' => 'supercalifragilistic' } }
28
+
29
+ before do
30
+ ## Since properties collections do subsequent queries on each individual properties records,
31
+ ## we need to additionally define stub requests for each of the individual records
32
+ additional_targets = [
33
+ described_class.new(client, attrs: { 'key' => 'foo' }, issue_id: '10002'),
34
+ described_class.new(client, attrs: { 'key' => 'xyz' }, issue_id: '10002')
35
+ ]
36
+ additional_targets.each do |target|
37
+ req_url = site_url + target.url
38
+ stub_request(:get, req_url).to_return(status: 200, body: get_mock_from_url(:get, req_url))
39
+ end
40
+ end
41
+
42
+ it_behaves_like 'a resource with a collection GET endpoint'
43
+ end
44
+ end
45
+ end
@@ -18,7 +18,7 @@ describe JIRA::Resource::RapidView do
18
18
  }
19
19
  end
20
20
 
21
- it_should_behave_like 'a resource'
21
+ it_behaves_like 'a resource'
22
22
  # TODO@Anton: Add json file
23
23
  # it_should_behave_like 'a resource with a singular GET endpoint'
24
24
 
@@ -26,19 +26,19 @@ describe JIRA::Resource::RapidView do
26
26
  let(:client) { client }
27
27
  let(:site_url) { site_url }
28
28
 
29
- before(:each) do
30
- stub_request(:get, site_url + '/jira/rest/greenhopper/1.0/rapidview')
29
+ before do
30
+ stub_request(:get, "#{site_url}/jira/rest/greenhopper/1.0/rapidview")
31
31
  .to_return(status: 200, body: get_mock_response('rapidview.json'))
32
32
  end
33
- it_should_behave_like 'a resource with a collection GET endpoint'
33
+
34
+ it_behaves_like 'a resource with a collection GET endpoint'
34
35
  end
35
36
 
36
37
  describe 'issues' do
37
- it 'should return all the issues' do
38
+ it 'returns all the issues' do
38
39
  stub_request(
39
40
  :get,
40
- site_url +
41
- '/jira/rest/greenhopper/1.0/xboard/plan/backlog/data?rapidViewId=1'
41
+ "#{site_url}/jira/rest/greenhopper/1.0/xboard/plan/backlog/data?rapidViewId=1"
42
42
  ).to_return(
43
43
  status: 200,
44
44
  body: get_mock_response('rapidview/SAMPLEPROJECT.issues.json')
@@ -46,7 +46,7 @@ describe JIRA::Resource::RapidView do
46
46
 
47
47
  stub_request(
48
48
  :get,
49
- site_url + '/jira/rest/api/2/search?jql=id IN(10001, 10000)'
49
+ "#{site_url}/jira/rest/api/2/search/jql?jql=id IN(10001, 10000)"
50
50
  ).to_return(
51
51
  status: 200,
52
52
  body: get_mock_response('rapidview/SAMPLEPROJECT.issues.full.json')
@@ -54,7 +54,7 @@ describe JIRA::Resource::RapidView do
54
54
 
55
55
  stub_request(
56
56
  :get,
57
- site_url + '/jira/rest/api/2/search?jql=id IN(10000, 10001) AND sprint IS NOT EMPTY'
57
+ "#{site_url}/jira/rest/api/2/search/jql?jql=id IN(10000, 10001) AND sprint IS NOT EMPTY"
58
58
  ).to_return(
59
59
  status: 200,
60
60
  body: get_mock_response('rapidview/SAMPLEPROJECT.issues.full.json')
@@ -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
@@ -19,8 +19,8 @@ describe JIRA::Resource::Resolution do
19
19
 
20
20
  let(:expected_collection_length) { 2 }
21
21
 
22
- it_should_behave_like 'a resource'
23
- it_should_behave_like 'a resource with a collection GET endpoint'
24
- it_should_behave_like 'a resource with a singular GET endpoint'
22
+ it_behaves_like 'a resource'
23
+ it_behaves_like 'a resource with a collection GET endpoint'
24
+ it_behaves_like 'a resource with a singular GET endpoint'
25
25
  end
26
26
  end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::StatusCategory do
4
+ with_each_client do |site_url, client|
5
+ let(:client) { client }
6
+ let(:site_url) { site_url }
7
+
8
+ let(:key) { 1 }
9
+
10
+ let(:expected_attributes) do
11
+ JSON.parse(File.read('spec/mock_responses/statuscategory/1.json'))
12
+ end
13
+
14
+ let(:expected_collection_length) { 4 }
15
+
16
+ it_behaves_like 'a resource'
17
+ it_behaves_like 'a resource with a collection GET endpoint'
18
+ it_behaves_like 'a resource with a singular GET endpoint'
19
+ end
20
+ end
@@ -8,17 +8,13 @@ describe JIRA::Resource::Status do
8
8
  let(:key) { '1' }
9
9
 
10
10
  let(:expected_attributes) do
11
- {
12
- 'self' => 'http://localhost:2990/jira/rest/api/2/status/1',
13
- 'id' => key,
14
- 'name' => 'Open'
15
- }
11
+ JSON.parse(File.read('spec/mock_responses/status/1.json'))
16
12
  end
17
13
 
18
14
  let(:expected_collection_length) { 5 }
19
15
 
20
- it_should_behave_like 'a resource'
21
- it_should_behave_like 'a resource with a collection GET endpoint'
22
- it_should_behave_like 'a resource with a singular GET endpoint'
16
+ it_behaves_like 'a resource'
17
+ it_behaves_like 'a resource with a collection GET endpoint'
18
+ it_behaves_like 'a resource with a singular GET endpoint'
23
19
  end
24
20
  end
@@ -7,7 +7,7 @@ describe JIRA::Resource::Transition do
7
7
 
8
8
  let(:key) { '10000' }
9
9
 
10
- let(:target) { JIRA::Resource::Transition.new(client, attrs: { 'id' => '99999' }, issue_id: '10014') }
10
+ let(:target) { described_class.new(client, attrs: { 'id' => '99999' }, issue_id: '10014') }
11
11
 
12
12
  let(:belongs_to) do
13
13
  JIRA::Resource::Issue.new(client, attrs: {
@@ -34,13 +34,14 @@ describe JIRA::Resource::Transition do
34
34
  }
35
35
  end
36
36
 
37
- it_should_behave_like 'a resource'
37
+ it_behaves_like 'a resource'
38
38
 
39
39
  describe 'POST endpoint' do
40
40
  it 'saves a new resource' do
41
- stub_request(:post, /#{described_class.collection_path(client, prefix)}$/)
41
+ req_url = build_url
42
+ stub_request(:post, req_url)
42
43
  .with(body: attributes_for_post.to_json)
43
- .to_return(status: 200, body: get_mock_from_path(:post))
44
+ .to_return(status: 200, body: get_mock_from_url(:post, req_url))
44
45
  subject = build_receiver.build
45
46
  expect(subject.save(attributes_for_post)).to be_truthy
46
47
  end
@@ -5,36 +5,59 @@ describe JIRA::Resource::User do
5
5
  let(:client) { client }
6
6
  let(:site_url) { site_url }
7
7
 
8
- let(:key) { 'admin' }
8
+ let(:key) { '1234567890abcdef01234567' }
9
9
 
10
10
  let(:expected_attributes) do
11
11
  {
12
- 'self' => 'http://localhost:2990/jira/rest/api/2/user?username=admin',
13
- 'name' => key,
14
- 'emailAddress' => 'admin@example.com'
12
+ 'id' => '1234567890abcdef01234567',
13
+ 'self' => 'http://localhost:2990/jira/rest/api/2/user?accountId=1234567890abcdef01234567',
14
+ 'name' => 'admin',
15
+ 'emailAddress' => 'admin@example.com',
16
+ 'avatarUrls' => {
17
+ '16x16' => 'http://localhost:2990/jira/secure/useravatar?size=small&avatarId=10122',
18
+ '48x48' => 'http://localhost:2990/jira/secure/useravatar?avatarId=10122'
19
+ },
20
+ 'displayName' => 'admin',
21
+ 'active' => true,
22
+ 'timeZone' => 'Pacific/Auckland',
23
+ 'groups' => {
24
+ 'size' => 3,
25
+ 'items' => []
26
+ },
27
+ 'expand' => 'groups'
15
28
  }
16
29
  end
17
30
 
18
- it_should_behave_like 'a resource'
19
- it_should_behave_like 'a resource with a singular GET endpoint'
31
+ it_behaves_like 'a resource'
32
+ it_behaves_like 'a resource with a singular GET endpoint'
20
33
 
21
34
  describe '#all' do
22
35
  let(:client) do
23
36
  client = double(options: { rest_base_path: '/jira/rest/api/2' })
24
- allow(client).to receive(:get).with('/rest/api/2/users/search?username=_&maxResults=1000').and_return(JIRA::Resource::UserFactory.new(client))
37
+ allow(client).to receive(:get).with('/rest/api/2/users/search?username=_&maxResults=1000')
38
+ .and_return(JIRA::Resource::UserFactory.new(client))
25
39
  client
26
40
  end
27
41
 
28
42
  before do
43
+ user_factory = double('UserFactory')
44
+
29
45
  allow(client).to receive(:get)
30
- .with('/rest/api/2/users/search?username=_&maxResults=1000') { OpenStruct.new(body: '["User1"]') }
31
- 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([])
32
50
  end
33
51
 
34
52
  it 'gets users with maxResults of 1000' do
53
+ user_factory = double('UserFactory')
54
+
35
55
  expect(client).to receive(:get).with('/rest/api/2/users/search?username=_&maxResults=1000')
36
- expect(client).to receive_message_chain(:User, :build).with('User1')
37
- JIRA::Resource::User.all(client)
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
+
60
+ described_class.all(client)
38
61
  end
39
62
  end
40
63
  end
@@ -10,7 +10,7 @@ describe JIRA::Resource::Version do
10
10
  let(:expected_attributes) do
11
11
  {
12
12
  'self' => 'http://localhost:2990/jira/rest/api/2/version/10000',
13
- 'id' => key,
13
+ 'id' => key,
14
14
  'description' => 'Initial version'
15
15
  }
16
16
  end
@@ -29,11 +29,11 @@ describe JIRA::Resource::Version do
29
29
  { 'id' => '10000', 'name' => '2.0.0' }
30
30
  end
31
31
 
32
- it_should_behave_like 'a resource'
33
- it_should_behave_like 'a resource with a singular GET endpoint'
34
- it_should_behave_like 'a resource with a DELETE endpoint'
35
- it_should_behave_like 'a resource with a POST endpoint'
36
- it_should_behave_like 'a resource with a PUT endpoint'
37
- it_should_behave_like 'a resource with a PUT endpoint that rejects invalid fields'
32
+ it_behaves_like 'a resource'
33
+ it_behaves_like 'a resource with a singular GET endpoint'
34
+ it_behaves_like 'a resource with a DELETE endpoint'
35
+ it_behaves_like 'a resource with a POST endpoint'
36
+ it_behaves_like 'a resource with a PUT endpoint'
37
+ it_behaves_like 'a resource with a PUT endpoint that rejects invalid fields'
38
38
  end
39
39
  end
@@ -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) { JIRA::Resource::Watcher.new(client, attrs: { 'id' => '99999' }, issue_id: '10002') }
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,47 @@ 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
- "isWatching": false,
23
- "watchCount": 1,
24
- "watchers": [
22
+ isWatching: false,
23
+ watchCount: 1,
24
+ watchers: [
25
25
  {
26
- "self": 'http://www.example.com/jira/rest/api/2/user?username=admin',
27
- "name": 'admin',
28
- "displayName": 'admin',
29
- "active": false
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(:each) do
37
- stub_request(:get, site_url + '/jira/rest/api/2/issue/10002')
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 + '/jira/rest/api/2/issue/10002/watchers')
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 + '/jira/rest/api/2/issue/10002/watchers')
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 'should returns all the watchers' do
47
+ it 'returnses all the watchers' do
48
48
  issue = client.Issue.find('10002')
49
- watchers = client.Watcher.all(options = { issue: issue })
49
+ watchers = client.Watcher.all({ issue: })
50
50
  expect(watchers.length).to eq(1)
51
51
  end
52
52
 
53
- it 'should add a watcher' do
53
+ it 'adds a watcher' do
54
54
  issue = client.Issue.find('10002')
55
- watcher = JIRA::Resource::Watcher.new(client, issue: issue)
56
- user_id = "tester"
55
+ watcher = described_class.new(client, issue: issue)
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
-
61
64
  end
62
65
  end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::Webhook do
4
+ ## This endpoint uses a different base path, so override this client's rest_base_path option
5
+ ## so this test can still use the SharedExampleGroups
6
+ with_each_client(rest_base_path: described_class.const_get(:REST_BASE_PATH)) do |site_url, client|
7
+ let(:client) { client }
8
+ let(:site_url) { site_url }
9
+
10
+ let(:key) { '2' }
11
+
12
+ let(:expected_attributes) do
13
+ { 'name' => 'from API', 'url' => 'http://localhost:3000/webhooks/1', 'excludeBody' => false,
14
+ '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 }
15
+ end
16
+
17
+ let(:expected_collection_length) { 1 }
18
+
19
+ it_behaves_like 'a resource'
20
+ it_behaves_like 'a resource with a collection GET endpoint'
21
+ it_behaves_like 'a resource with a singular GET endpoint'
22
+
23
+ it 'returns a collection of components' do
24
+ stub_request(:get, site_url + described_class.singular_path(client, key))
25
+ .to_return(status: 200, body: get_mock_response('webhook/2.json'))
26
+
27
+ webhook = client.Webhook.find(key)
28
+
29
+ expect(webhook).to be_a described_class
30
+ expect(webhook.name).to eq 'from API'
31
+ expect(webhook.url).to eq '/jira/rest/webhooks/1.0/webhook/2'
32
+ expect(webhook.enabled).to be true
33
+ end
34
+ end
35
+ end
@@ -7,7 +7,7 @@ describe JIRA::Resource::Worklog do
7
7
 
8
8
  let(:key) { '10000' }
9
9
 
10
- let(:target) { JIRA::Resource::Worklog.new(client, attrs: { 'id' => '99999' }, issue_id: '54321') }
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' => key,
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
- it_should_behave_like 'a resource'
45
- it_should_behave_like 'a resource with a collection GET endpoint'
46
- it_should_behave_like 'a resource with a singular GET endpoint'
47
- it_should_behave_like 'a resource with a DELETE endpoint'
48
- it_should_behave_like 'a resource with a POST endpoint'
49
- it_should_behave_like 'a resource with a PUT endpoint'
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
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ # rubocop:disable RSpec/LeakyLocalVariable
4
+ describe JIRA::Atlassian::Jwt do
5
+ let(:jwt_opts) do
6
+ {
7
+ algorithm: 'HS256',
8
+ leeway: (3600 * 24 * 365 * 10) # 10 years of leeway -- the JWT gem verifies the token expiry time
9
+ }
10
+ end
11
+ let(:base_url) { '' }
12
+
13
+ it 'generates claims' do
14
+ url = 'https://example.atlassian.com/jira/projects'
15
+ issuer = 'com.atlassian.test'
16
+
17
+ now = Time.now.to_i
18
+ qsh = Digest::SHA256.hexdigest(
19
+ described_class.create_canonical_request(url, 'get', base_url)
20
+ )
21
+
22
+ expected_claim = {
23
+ iss: 'com.atlassian.test',
24
+ iat: now,
25
+ exp: now + 60,
26
+ qsh: qsh
27
+ }
28
+
29
+ claim = described_class.build_claims(issuer, url, 'get', base_url, now, now + 60)
30
+ expect(claim).to eq expected_claim
31
+ end
32
+
33
+ # Offical Atlassian signed URL test data
34
+ json_tests = File.read(File.expand_path('../../data/files/jwt-signed-urls.json', File.dirname(__FILE__)))
35
+
36
+ test_data = JSON.parse(json_tests)
37
+ shared_secret = test_data['secret']
38
+
39
+ test_data['tests'].each do |test|
40
+ signed_url = test['signedUrl']
41
+ signed_uri = URI.parse(signed_url)
42
+ token = CGI.parse(signed_uri.query)['jwt'].first
43
+
44
+ it "#{test['name']} - Canonical URL" do
45
+ canonical_uri = described_class.create_canonical_request(signed_url, 'GET', base_url)
46
+
47
+ # Remote the jwt query param from the signed URL to get the original
48
+ expect(canonical_uri).to eq test['canonicalUrl']
49
+ end
50
+
51
+ it "#{test['name']} - QSH match" do
52
+ expected_qsh = Digest::SHA256.hexdigest(described_class.create_canonical_request(signed_url, 'GET', base_url))
53
+
54
+ decoded_token = JWT.decode(token, shared_secret, true, jwt_opts).first
55
+
56
+ expect(expected_qsh).to eq decoded_token['qsh']
57
+ end
58
+ end
59
+ end
60
+ # rubocop:enable RSpec/LeakyLocalVariable
@@ -1,12 +1,22 @@
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
- let(:client) { double }
8
16
  subject { JIRA::Resource::FooFactory.new(client) }
9
17
 
18
+ let(:client) { double }
19
+
10
20
  it 'initializes correctly' do
11
21
  expect(subject.class).to eq(JIRA::Resource::FooFactory)
12
22
  expect(subject.client).to eq(client)