jira-ruby 2.1.3

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 (154) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.travis.yml +9 -0
  4. data/Gemfile +14 -0
  5. data/Guardfile +14 -0
  6. data/LICENSE +19 -0
  7. data/README.md +427 -0
  8. data/Rakefile +31 -0
  9. data/example.rb +224 -0
  10. data/http-basic-example.rb +113 -0
  11. data/jira-ruby.gemspec +35 -0
  12. data/lib/jira-ruby.rb +49 -0
  13. data/lib/jira/base.rb +525 -0
  14. data/lib/jira/base_factory.rb +46 -0
  15. data/lib/jira/client.rb +308 -0
  16. data/lib/jira/has_many_proxy.rb +42 -0
  17. data/lib/jira/http_client.rb +112 -0
  18. data/lib/jira/http_error.rb +14 -0
  19. data/lib/jira/jwt_client.rb +67 -0
  20. data/lib/jira/oauth_client.rb +114 -0
  21. data/lib/jira/railtie.rb +10 -0
  22. data/lib/jira/request_client.rb +31 -0
  23. data/lib/jira/resource/agile.rb +79 -0
  24. data/lib/jira/resource/applinks.rb +39 -0
  25. data/lib/jira/resource/attachment.rb +50 -0
  26. data/lib/jira/resource/board.rb +91 -0
  27. data/lib/jira/resource/board_configuration.rb +9 -0
  28. data/lib/jira/resource/comment.rb +12 -0
  29. data/lib/jira/resource/component.rb +8 -0
  30. data/lib/jira/resource/createmeta.rb +44 -0
  31. data/lib/jira/resource/field.rb +83 -0
  32. data/lib/jira/resource/filter.rb +15 -0
  33. data/lib/jira/resource/issue.rb +141 -0
  34. data/lib/jira/resource/issuelink.rb +20 -0
  35. data/lib/jira/resource/issuelinktype.rb +14 -0
  36. data/lib/jira/resource/issuetype.rb +8 -0
  37. data/lib/jira/resource/priority.rb +8 -0
  38. data/lib/jira/resource/project.rb +41 -0
  39. data/lib/jira/resource/rapidview.rb +67 -0
  40. data/lib/jira/resource/remotelink.rb +26 -0
  41. data/lib/jira/resource/resolution.rb +8 -0
  42. data/lib/jira/resource/serverinfo.rb +18 -0
  43. data/lib/jira/resource/sprint.rb +105 -0
  44. data/lib/jira/resource/sprint_report.rb +8 -0
  45. data/lib/jira/resource/status.rb +8 -0
  46. data/lib/jira/resource/transition.rb +29 -0
  47. data/lib/jira/resource/user.rb +30 -0
  48. data/lib/jira/resource/version.rb +8 -0
  49. data/lib/jira/resource/watcher.rb +35 -0
  50. data/lib/jira/resource/webhook.rb +37 -0
  51. data/lib/jira/resource/worklog.rb +14 -0
  52. data/lib/jira/tasks.rb +0 -0
  53. data/lib/jira/version.rb +3 -0
  54. data/lib/tasks/generate.rake +18 -0
  55. data/spec/integration/attachment_spec.rb +32 -0
  56. data/spec/integration/comment_spec.rb +52 -0
  57. data/spec/integration/component_spec.rb +39 -0
  58. data/spec/integration/field_spec.rb +32 -0
  59. data/spec/integration/issue_spec.rb +93 -0
  60. data/spec/integration/issuelinktype_spec.rb +26 -0
  61. data/spec/integration/issuetype_spec.rb +24 -0
  62. data/spec/integration/priority_spec.rb +24 -0
  63. data/spec/integration/project_spec.rb +49 -0
  64. data/spec/integration/rapidview_spec.rb +74 -0
  65. data/spec/integration/resolution_spec.rb +26 -0
  66. data/spec/integration/status_spec.rb +24 -0
  67. data/spec/integration/transition_spec.rb +49 -0
  68. data/spec/integration/user_spec.rb +41 -0
  69. data/spec/integration/version_spec.rb +39 -0
  70. data/spec/integration/watcher_spec.rb +62 -0
  71. data/spec/integration/webhook.rb +25 -0
  72. data/spec/integration/worklog_spec.rb +51 -0
  73. data/spec/jira/base_factory_spec.rb +45 -0
  74. data/spec/jira/base_spec.rb +598 -0
  75. data/spec/jira/client_spec.rb +291 -0
  76. data/spec/jira/has_many_proxy_spec.rb +46 -0
  77. data/spec/jira/http_client_spec.rb +328 -0
  78. data/spec/jira/http_error_spec.rb +24 -0
  79. data/spec/jira/jwt_uri_builder_spec.rb +59 -0
  80. data/spec/jira/oauth_client_spec.rb +162 -0
  81. data/spec/jira/request_client_spec.rb +41 -0
  82. data/spec/jira/resource/agile_spec.rb +135 -0
  83. data/spec/jira/resource/attachment_spec.rb +138 -0
  84. data/spec/jira/resource/board_spec.rb +224 -0
  85. data/spec/jira/resource/createmeta_spec.rb +258 -0
  86. data/spec/jira/resource/field_spec.rb +85 -0
  87. data/spec/jira/resource/filter_spec.rb +97 -0
  88. data/spec/jira/resource/issue_spec.rb +227 -0
  89. data/spec/jira/resource/issuelink_spec.rb +14 -0
  90. data/spec/jira/resource/project_factory_spec.rb +11 -0
  91. data/spec/jira/resource/project_spec.rb +123 -0
  92. data/spec/jira/resource/sprint_spec.rb +90 -0
  93. data/spec/jira/resource/user_factory_spec.rb +31 -0
  94. data/spec/jira/resource/worklog_spec.rb +22 -0
  95. data/spec/mock_responses/board/1.json +33 -0
  96. data/spec/mock_responses/board/1_issues.json +62 -0
  97. data/spec/mock_responses/component.post.json +28 -0
  98. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  99. data/spec/mock_responses/component/10000.json +39 -0
  100. data/spec/mock_responses/component/10000.put.json +39 -0
  101. data/spec/mock_responses/empty_issues.json +8 -0
  102. data/spec/mock_responses/field.json +32 -0
  103. data/spec/mock_responses/field/1.json +15 -0
  104. data/spec/mock_responses/issue.json +1108 -0
  105. data/spec/mock_responses/issue.post.json +5 -0
  106. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  107. data/spec/mock_responses/issue/10002.json +126 -0
  108. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  109. data/spec/mock_responses/issue/10002/attachments/10000.json +20 -0
  110. data/spec/mock_responses/issue/10002/comment.json +65 -0
  111. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  112. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  113. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  114. data/spec/mock_responses/issue/10002/transitions.json +49 -0
  115. data/spec/mock_responses/issue/10002/transitions.post.json +1 -0
  116. data/spec/mock_responses/issue/10002/watchers.json +13 -0
  117. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  118. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  119. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  120. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  121. data/spec/mock_responses/issueLinkType.json +25 -0
  122. data/spec/mock_responses/issueLinkType/10000.json +7 -0
  123. data/spec/mock_responses/issuetype.json +42 -0
  124. data/spec/mock_responses/issuetype/5.json +8 -0
  125. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook.json +11 -0
  126. data/spec/mock_responses/jira/rest/webhooks/1.0/webhook/2.json +11 -0
  127. data/spec/mock_responses/priority.json +42 -0
  128. data/spec/mock_responses/priority/1.json +8 -0
  129. data/spec/mock_responses/project.json +12 -0
  130. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  131. data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
  132. data/spec/mock_responses/rapidview.json +10 -0
  133. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.full.json +276 -0
  134. data/spec/mock_responses/rapidview/SAMPLEPROJECT.issues.json +111 -0
  135. data/spec/mock_responses/rapidview/SAMPLEPROJECT.json +6 -0
  136. data/spec/mock_responses/resolution.json +15 -0
  137. data/spec/mock_responses/resolution/1.json +7 -0
  138. data/spec/mock_responses/sprint/1_issues.json +125 -0
  139. data/spec/mock_responses/status.json +37 -0
  140. data/spec/mock_responses/status/1.json +7 -0
  141. data/spec/mock_responses/user_username=admin.json +17 -0
  142. data/spec/mock_responses/version.post.json +7 -0
  143. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  144. data/spec/mock_responses/version/10000.json +11 -0
  145. data/spec/mock_responses/version/10000.put.json +7 -0
  146. data/spec/mock_responses/webhook.json +11 -0
  147. data/spec/mock_responses/webhook/webhook.json +11 -0
  148. data/spec/spec_helper.rb +21 -0
  149. data/spec/support/clients_helper.rb +16 -0
  150. data/spec/support/matchers/have_attributes.rb +11 -0
  151. data/spec/support/matchers/have_many.rb +9 -0
  152. data/spec/support/matchers/have_one.rb +5 -0
  153. data/spec/support/shared_examples/integration.rb +177 -0
  154. metadata +491 -0
@@ -0,0 +1,291 @@
1
+ require 'spec_helper'
2
+
3
+ # We have three forms of authentication with two clases to represent the client for those different authentications.
4
+ # Some behaviours are shared across all three types of authentication. these are captured here.
5
+ RSpec.shared_examples 'Client Common Tests' do
6
+ it { is_expected.to be_a JIRA::Client }
7
+
8
+ it 'freezes the options once initialised' do
9
+ expect(subject.options).to be_frozen
10
+ end
11
+
12
+ it 'prepends context path to rest base_path' do
13
+ options = [:rest_base_path]
14
+ defaults = JIRA::Client::DEFAULT_OPTIONS
15
+ options.each { |key| expect(subject.options[key]).to eq(defaults[:context_path] + defaults[key]) }
16
+ end
17
+
18
+ it 'merges headers' do
19
+ expect(subject.send(:merge_default_headers, {})).to eq('Accept' => 'application/json')
20
+ end
21
+
22
+ describe 'http methods' do
23
+ it 'merges default headers' do
24
+ # stubbed response for generic client request method
25
+ expect(subject).to receive(:request).exactly(5).times.and_return(successful_response)
26
+
27
+ # response for merging headers for http methods with no body
28
+ expect(subject).to receive(:merge_default_headers).exactly(3).times.with({})
29
+
30
+ # response for merging headers for http methods with body
31
+ expect(subject).to receive(:merge_default_headers).exactly(2).times.with(content_type_header)
32
+
33
+ %i[delete get head].each { |method| subject.send(method, '/path', {}) }
34
+ %i[post put].each { |method| subject.send(method, '/path', '', content_type_header) }
35
+ end
36
+
37
+ it 'calls the generic request method' do
38
+ %i[delete get head].each do |method|
39
+ expect(subject).to receive(:request).with(method, '/path', nil, headers).and_return(successful_response)
40
+ subject.send(method, '/path', {})
41
+ end
42
+
43
+ %i[post put].each do |method|
44
+ expect(subject).to receive(:request).with(method, '/path', '', merged_headers)
45
+ subject.send(method, '/path', '', {})
46
+ end
47
+ end
48
+ end
49
+
50
+ describe 'Resource Factories' do
51
+ it 'gets all projects' do
52
+ expect(JIRA::Resource::Project).to receive(:all).with(subject).and_return([])
53
+ expect(subject.Project.all).to eq([])
54
+ end
55
+
56
+ it 'finds a single project' do
57
+ find_result = double
58
+ expect(JIRA::Resource::Project).to receive(:find).with(subject, '123').and_return(find_result)
59
+ expect(subject.Project.find('123')).to eq(find_result)
60
+ end
61
+ end
62
+
63
+ describe 'SSL client options' do
64
+ context 'without certificate and key' do
65
+ let(:options) { { use_client_cert: true } }
66
+ subject { JIRA::Client.new(options) }
67
+
68
+ it 'raises an ArgumentError' do
69
+ expect { subject }.to raise_exception(ArgumentError, 'Options: :cert_path or :ssl_client_cert must be set when :use_client_cert is true')
70
+ options[:ssl_client_cert] = '<cert></cert>'
71
+ expect { subject }.to raise_exception(ArgumentError, 'Options: :key_path or :ssl_client_key must be set when :use_client_cert is true')
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ RSpec.shared_examples 'HttpClient tests' do
78
+ it 'makes a valid request' do
79
+ %i[delete get head].each do |method|
80
+ expect(subject.request_client).to receive(:make_request).with(method, '/path', nil, headers).and_return(successful_response)
81
+ subject.send(method, '/path', headers)
82
+ end
83
+ %i[post put].each do |method|
84
+ expect(subject.request_client).to receive(:make_request).with(method, '/path', '', merged_headers).and_return(successful_response)
85
+ subject.send(method, '/path', '', headers)
86
+ end
87
+ end
88
+ end
89
+
90
+ RSpec.shared_examples 'OAuth Common Tests' do
91
+ include_examples 'Client Common Tests'
92
+
93
+ specify { expect(subject.request_client).to be_a JIRA::OauthClient }
94
+
95
+ it 'allows setting an access token' do
96
+ token = double
97
+ expect(OAuth::AccessToken).to receive(:new).with(subject.consumer, '', '').and_return(token)
98
+
99
+ expect(subject.authenticated?).to be_falsey
100
+ access_token = subject.set_access_token('', '')
101
+ expect(access_token).to eq(token)
102
+ expect(subject.access_token).to eq(token)
103
+ expect(subject.authenticated?).to be_truthy
104
+ end
105
+
106
+ describe 'that call a oauth client' do
107
+ specify 'which makes a request' do
108
+ %i[delete get head].each do |method|
109
+ expect(subject.request_client).to receive(:make_request).with(method, '/path', nil, headers).and_return(successful_response)
110
+ subject.send(method, '/path', {})
111
+ end
112
+ %i[post put].each do |method|
113
+ expect(subject.request_client).to receive(:make_request).with(method, '/path', '', merged_headers).and_return(successful_response)
114
+ subject.send(method, '/path', '', {})
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ describe JIRA::Client do
121
+ let(:request) { subject.request_client.class }
122
+ let(:successful_response) do
123
+ response = double('response')
124
+ allow(response).to receive(:is_a?).with(Net::HTTPSuccess).and_return(true)
125
+ response
126
+ end
127
+ let(:content_type_header) { { 'Content-Type' => 'application/json' } }
128
+ let(:headers) { { 'Accept' => 'application/json' } }
129
+ let(:merged_headers) { headers.merge(content_type_header) }
130
+
131
+ context 'behaviour that applies to all client classes irrespective of authentication method' do
132
+ it 'allows the overriding of some options' do
133
+ client = JIRA::Client.new(consumer_key: 'foo', consumer_secret: 'bar', site: 'http://foo.com/')
134
+ expect(client.options[:site]).to eq('http://foo.com/')
135
+ expect(JIRA::Client::DEFAULT_OPTIONS[:site]).not_to eq('http://foo.com/')
136
+ end
137
+ end
138
+
139
+ context 'with basic http authentication' do
140
+ subject { JIRA::Client.new(username: 'foo', password: 'bar', auth_type: :basic) }
141
+
142
+ before(:each) do
143
+ stub_request(:get, 'https://foo:bar@localhost:2990/jira/rest/api/2/project')
144
+ .to_return(status: 200, body: '[]', headers: {})
145
+
146
+ stub_request(:get, 'https://foo:badpassword@localhost:2990/jira/rest/api/2/project')
147
+ .to_return(status: 401, headers: {})
148
+ end
149
+
150
+ include_examples 'Client Common Tests'
151
+ include_examples 'HttpClient tests'
152
+
153
+ specify { expect(subject.request_client).to be_a JIRA::HttpClient }
154
+
155
+ it 'sets the username and password' do
156
+ expect(subject.options[:username]).to eq('foo')
157
+ expect(subject.options[:password]).to eq('bar')
158
+ end
159
+
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
+ it 'only returns a true for #authenticated? once we have requested some data' do
167
+ expect(subject.authenticated?).to be_falsey
168
+ expect(subject.Project.all).to be_empty
169
+ expect(subject.authenticated?).to be_truthy
170
+ end
171
+ end
172
+
173
+ context 'with cookie authentication' do
174
+ subject { JIRA::Client.new(username: 'foo', password: 'bar', auth_type: :cookie) }
175
+
176
+ let(:session_cookie) { '6E3487971234567896704A9EB4AE501F' }
177
+ let(:session_body) do
178
+ {
179
+ 'session': { 'name' => 'JSESSIONID', 'value' => session_cookie },
180
+ 'loginInfo': { 'failedLoginCount' => 1, 'loginCount' => 2,
181
+ 'lastFailedLoginTime' => (DateTime.now - 2).iso8601,
182
+ 'previousLoginTime' => (DateTime.now - 5).iso8601 }
183
+ }
184
+ end
185
+
186
+ before(:each) do
187
+ # General case of API call with no authentication, or wrong authentication
188
+ stub_request(:post, 'https://localhost:2990/jira/rest/auth/1/session')
189
+ .to_return(status: 401, headers: {})
190
+
191
+ # Now special case of API with correct authentication. This gets checked first by RSpec.
192
+ stub_request(:post, 'https://localhost:2990/jira/rest/auth/1/session')
193
+ .with(body: '{"username":"foo","password":"bar"}')
194
+ .to_return(status: 200, body: session_body.to_json,
195
+ headers: { 'Set-Cookie': "JSESSIONID=#{session_cookie}; Path=/; HttpOnly" })
196
+
197
+ stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
198
+ .with(headers: { cookie: "JSESSIONID=#{session_cookie}" })
199
+ .to_return(status: 200, body: '[]', headers: {})
200
+ end
201
+
202
+ include_examples 'Client Common Tests'
203
+ include_examples 'HttpClient tests'
204
+
205
+ specify { expect(subject.request_client).to be_a JIRA::HttpClient }
206
+
207
+ it 'authenticates with a correct username and password' do
208
+ expect(subject).to be_authenticated
209
+ expect(subject.Project.all).to be_empty
210
+ end
211
+
212
+ it 'does not authenticate with an incorrect username and password' do
213
+ bad_client = JIRA::Client.new(username: 'foo', password: 'bad_password', auth_type: :cookie)
214
+ expect(bad_client).not_to be_authenticated
215
+ end
216
+
217
+ it 'destroys the username and password once authenticated' do
218
+ expect(subject.options[:username]).to be_nil
219
+ expect(subject.options[:password]).to be_nil
220
+ end
221
+ end
222
+
223
+ context 'with jwt authentication' do
224
+ subject do
225
+ JIRA::Client.new(
226
+ issuer: 'foo',
227
+ base_url: 'https://host.tld',
228
+ shared_secret: 'shared_secret_key',
229
+ auth_type: :jwt
230
+ )
231
+ end
232
+
233
+ before(:each) do
234
+ stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
235
+ .with(query: hash_including(:jwt))
236
+ .to_return(status: 200, body: '[]', headers: {})
237
+ end
238
+
239
+ include_examples 'Client Common Tests'
240
+ include_examples 'HttpClient tests'
241
+
242
+ specify { expect(subject.request_client).to be_a JIRA::JwtClient }
243
+
244
+ it 'sets the username and password' do
245
+ expect(subject.options[:shared_secret]).to eq('shared_secret_key')
246
+ end
247
+
248
+ context 'with a incorrect jwt key' do
249
+ before do
250
+ stub_request(:get, 'https://localhost:2990/jira/rest/api/2/project')
251
+ .with(query: hash_including(:jwt))
252
+ .to_return(status: 401, body: '[]', headers: {})
253
+ end
254
+
255
+ it 'is not authenticated' do
256
+ expect(subject.authenticated?).to be_falsey
257
+ end
258
+
259
+ it 'raises a JIRA::HTTPError when trying to fetch projects' do
260
+ expect { subject.Project.all }.to raise_error JIRA::HTTPError
261
+ end
262
+ end
263
+
264
+ it 'only returns a true for #authenticated? once we have requested some data' do
265
+ expect(subject.authenticated?).to be_falsey
266
+ expect(subject.Project.all).to be_empty
267
+ expect(subject.authenticated?).to be_truthy
268
+ end
269
+ end
270
+
271
+ context 'oauth authentication' do
272
+ subject { JIRA::Client.new(consumer_key: 'foo', consumer_secret: 'bar') }
273
+
274
+ include_examples 'OAuth Common Tests'
275
+ end
276
+
277
+ context 'with oauth_2legged' do
278
+ subject { JIRA::Client.new(consumer_key: 'foo', consumer_secret: 'bar', auth_type: :oauth_2legged) }
279
+
280
+ include_examples 'OAuth Common Tests'
281
+ end
282
+
283
+ context 'with unknown options' do
284
+ let(:options) { { 'username' => 'foo', 'password' => 'bar', auth_type: :basic } }
285
+ subject { JIRA::Client.new(options) }
286
+
287
+ it 'raises an ArgumentError' do
288
+ expect { subject }.to raise_exception(ArgumentError, 'Unknown option(s) given: ["username", "password"]')
289
+ end
290
+ end
291
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::HasManyProxy do
4
+ class Foo; end
5
+
6
+ subject { JIRA::HasManyProxy.new(parent, Foo, collection) }
7
+
8
+ let(:parent) { double('parent') }
9
+ let(:collection) { double('collection') }
10
+
11
+ it 'has a target class' do
12
+ expect(subject.target_class).to eq(Foo)
13
+ end
14
+
15
+ it 'has a parent' do
16
+ expect(subject.parent).to eq(parent)
17
+ end
18
+
19
+ it 'has a collection' do
20
+ expect(subject.collection).to eq(collection)
21
+ end
22
+
23
+ it 'can build a new instance' do
24
+ client = double('client')
25
+ foo = double('foo')
26
+ allow(parent).to receive(:client).and_return(client)
27
+ allow(parent).to receive(:to_sym).and_return(:parent)
28
+ expect(Foo).to receive(:new).with(client, attrs: { 'foo' => 'bar' }, parent: parent).and_return(foo)
29
+ expect(collection).to receive(:<<).with(foo)
30
+ expect(subject.build('foo' => 'bar')).to eq(foo)
31
+ end
32
+
33
+ it 'can get all the instances' do
34
+ foo = double('foo')
35
+ client = double('client')
36
+ allow(parent).to receive(:client).and_return(client)
37
+ allow(parent).to receive(:to_sym).and_return(:parent)
38
+ expect(Foo).to receive(:all).with(client, parent: parent).and_return(foo)
39
+ expect(subject.all).to eq(foo)
40
+ end
41
+
42
+ it 'delegates missing methods to the collection' do
43
+ expect(collection).to receive(:missing_method)
44
+ subject.missing_method
45
+ end
46
+ end
@@ -0,0 +1,328 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::HttpClient do
4
+ let(:basic_client) do
5
+ options = JIRA::Client::DEFAULT_OPTIONS
6
+ .merge(JIRA::HttpClient::DEFAULT_OPTIONS)
7
+ .merge(basic_auth_credentials)
8
+ JIRA::HttpClient.new(options)
9
+ end
10
+
11
+ let(:basic_cookie_client) do
12
+ options = JIRA::Client::DEFAULT_OPTIONS
13
+ .merge(JIRA::HttpClient::DEFAULT_OPTIONS)
14
+ .merge(use_cookies: true)
15
+ .merge(basic_auth_credentials)
16
+ JIRA::HttpClient.new(options)
17
+ end
18
+
19
+ let(:custom_ssl_version_client) do
20
+ options = JIRA::Client::DEFAULT_OPTIONS.merge(JIRA::HttpClient::DEFAULT_OPTIONS).merge(ssl_version: :TLSv1_2)
21
+ JIRA::HttpClient.new(options)
22
+ end
23
+
24
+ let(:basic_cookie_client_with_context_path) do
25
+ options = JIRA::Client::DEFAULT_OPTIONS.merge(JIRA::HttpClient::DEFAULT_OPTIONS).merge(
26
+ use_cookies: true,
27
+ context_path: '/context'
28
+ )
29
+ JIRA::HttpClient.new(options)
30
+ end
31
+
32
+ let(:basic_cookie_client_with_additional_cookies) do
33
+ options = JIRA::Client::DEFAULT_OPTIONS
34
+ .merge(JIRA::HttpClient::DEFAULT_OPTIONS)
35
+ .merge(
36
+ use_cookies: true,
37
+ additional_cookies: ['sessionToken=abc123', 'internal=true']
38
+ )
39
+ .merge(basic_auth_credentials)
40
+ JIRA::HttpClient.new(options)
41
+ end
42
+
43
+ let(:basic_client_cert_client) do
44
+ options = JIRA::Client::DEFAULT_OPTIONS.merge(JIRA::HttpClient::DEFAULT_OPTIONS).merge(
45
+ use_client_cert: true,
46
+ cert: 'public certificate contents',
47
+ key: 'private key contents'
48
+ )
49
+ JIRA::HttpClient.new(options)
50
+ end
51
+
52
+ let(:basic_client_with_no_auth_credentials) do
53
+ options = JIRA::Client::DEFAULT_OPTIONS
54
+ .merge(JIRA::HttpClient::DEFAULT_OPTIONS)
55
+ JIRA::HttpClient.new(options)
56
+ end
57
+
58
+ let(:basic_auth_credentials) do
59
+ { username: 'donaldduck', password: 'supersecret' }
60
+ end
61
+
62
+ let(:proxy_client) do
63
+ options = JIRA::Client::DEFAULT_OPTIONS.merge(JIRA::HttpClient::DEFAULT_OPTIONS).merge(
64
+ proxy_address: 'proxyAddress',
65
+ proxy_port: 42,
66
+ proxy_username: 'proxyUsername',
67
+ proxy_password: 'proxyPassword'
68
+ )
69
+ JIRA::HttpClient.new(options)
70
+ end
71
+
72
+ let(:response) do
73
+ response = double('response')
74
+ allow(response).to receive(:kind_of?).with(Net::HTTPSuccess).and_return(true)
75
+ response
76
+ end
77
+
78
+ let(:cookie_response) do
79
+ response = double('response')
80
+ allow(response).to receive(:kind_of?).with(Net::HTTPSuccess).and_return(true)
81
+ response
82
+ end
83
+
84
+ it 'creates an instance of Net:HTTP for a basic auth client' do
85
+ expect(basic_client.basic_auth_http_conn.class).to eq(Net::HTTP)
86
+ end
87
+
88
+ it 'makes a correct HTTP request for make_cookie_auth_request' do
89
+ request = double
90
+ basic_auth_http_conn = double
91
+
92
+ headers = { 'Content-Type' => 'application/json' }
93
+ expected_path = '/context/rest/auth/1/session'
94
+ expected_body = '{"username":"","password":""}'
95
+
96
+ allow(basic_cookie_client_with_context_path).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
97
+ expect(basic_auth_http_conn).to receive(:request).with(request).and_return(response)
98
+
99
+ allow(request).to receive(:basic_auth)
100
+ allow(response).to receive(:get_fields).with('set-cookie')
101
+
102
+ expect(request).to receive(:body=).with(expected_body)
103
+ expect(Net::HTTP.const_get(:post.to_s.capitalize)).to receive(:new).with(expected_path, headers).and_return(request)
104
+
105
+ basic_cookie_client_with_context_path.make_cookie_auth_request
106
+ end
107
+
108
+ it 'responds to the http methods' do
109
+ body = ''
110
+ headers = double
111
+ basic_auth_http_conn = double
112
+ request = double
113
+ allow(basic_client).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
114
+ expect(request).to receive(:basic_auth).with(basic_client.options[:username], basic_client.options[:password]).exactly(5).times.and_return(request)
115
+ expect(basic_auth_http_conn).to receive(:request).exactly(5).times.with(request).and_return(response)
116
+ %i[delete get head].each do |method|
117
+ expect(Net::HTTP.const_get(method.to_s.capitalize)).to receive(:new).with('/path', headers).and_return(request)
118
+ expect(basic_client.make_request(method, '/path', nil, headers)).to eq(response)
119
+ end
120
+ %i[post put].each do |method|
121
+ expect(Net::HTTP.const_get(method.to_s.capitalize)).to receive(:new).with('/path', headers).and_return(request)
122
+ expect(request).to receive(:body=).with(body).and_return(request)
123
+ expect(basic_client.make_request(method, '/path', body, headers)).to eq(response)
124
+ end
125
+ end
126
+
127
+ it 'gets and sets cookies' do
128
+ body = ''
129
+ headers = double
130
+ basic_auth_http_conn = double
131
+ request = double
132
+ allow(basic_cookie_client).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
133
+ expect(request).to receive(:basic_auth).with(basic_cookie_client.options[:username], basic_cookie_client.options[:password]).exactly(5).times.and_return(request)
134
+ expect(cookie_response).to receive(:get_fields).with('set-cookie').exactly(5).times
135
+ expect(basic_auth_http_conn).to receive(:request).exactly(5).times.with(request).and_return(cookie_response)
136
+ %i[delete get head].each do |method|
137
+ expect(Net::HTTP.const_get(method.to_s.capitalize)).to receive(:new).with('/path', headers).and_return(request)
138
+ expect(basic_cookie_client.make_request(method, '/path', nil, headers)).to eq(cookie_response)
139
+ end
140
+ %i[post put].each do |method|
141
+ expect(Net::HTTP.const_get(method.to_s.capitalize)).to receive(:new).with('/path', headers).and_return(request)
142
+ expect(request).to receive(:body=).with(body).and_return(request)
143
+ expect(basic_cookie_client.make_request(method, '/path', body, headers)).to eq(cookie_response)
144
+ end
145
+ end
146
+
147
+ it 'sets additional cookies when they are provided' do
148
+ client = basic_cookie_client_with_additional_cookies
149
+ body = ''
150
+ headers = double
151
+ basic_auth_http_conn = double
152
+ request = double
153
+ allow(client).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
154
+ expect(request).to receive(:basic_auth).with(client.options[:username], client.options[:password]).exactly(5).times.and_return(request)
155
+ expect(request).to receive(:add_field).with('Cookie', 'sessionToken=abc123; internal=true').exactly(5).times
156
+ expect(cookie_response).to receive(:get_fields).with('set-cookie').exactly(5).times
157
+ expect(basic_auth_http_conn).to receive(:request).exactly(5).times.with(request).and_return(cookie_response)
158
+ %i[delete get head].each do |method|
159
+ expect(Net::HTTP.const_get(method.to_s.capitalize)).to receive(:new).with('/path', headers).and_return(request)
160
+ expect(client.make_request(method, '/path', nil, headers)).to eq(cookie_response)
161
+ end
162
+ %i[post put].each do |method|
163
+ expect(Net::HTTP.const_get(method.to_s.capitalize)).to receive(:new).with('/path', headers).and_return(request)
164
+ expect(request).to receive(:body=).with(body).and_return(request)
165
+ expect(client.make_request(method, '/path', body, headers)).to eq(cookie_response)
166
+ end
167
+ end
168
+
169
+ it 'performs a basic http client request' do
170
+ body = nil
171
+ headers = double
172
+ basic_auth_http_conn = double
173
+ http_request = double
174
+ expect(Net::HTTP::Get).to receive(:new).with('/foo', headers).and_return(http_request)
175
+
176
+ expect(basic_auth_http_conn).to receive(:request).with(http_request).and_return(response)
177
+ expect(http_request).to receive(:basic_auth).with(basic_client.options[:username], basic_client.options[:password]).and_return(http_request)
178
+ allow(basic_client).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
179
+ basic_client.make_request(:get, '/foo', body, headers)
180
+ end
181
+
182
+ it 'performs a basic http client request with a full domain' do
183
+ body = nil
184
+ headers = double
185
+ basic_auth_http_conn = double
186
+ http_request = double
187
+ expect(Net::HTTP::Get).to receive(:new).with('/foo', headers).and_return(http_request)
188
+
189
+ expect(basic_auth_http_conn).to receive(:request).with(http_request).and_return(response)
190
+ expect(http_request).to receive(:basic_auth).with(basic_client.options[:username], basic_client.options[:password]).and_return(http_request)
191
+ allow(basic_client).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
192
+ basic_client.make_request(:get, 'http://mydomain.com/foo', body, headers)
193
+ end
194
+
195
+ it 'does not try to use basic auth if the credentials are not set' do
196
+ body = nil
197
+ headers = double
198
+ basic_auth_http_conn = double
199
+ http_request = double
200
+ expect(Net::HTTP::Get).to receive(:new).with('/foo', headers).and_return(http_request)
201
+
202
+ expect(basic_auth_http_conn).to receive(:request).with(http_request).and_return(response)
203
+ expect(http_request).not_to receive(:basic_auth)
204
+ allow(basic_client_with_no_auth_credentials).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
205
+ basic_client_with_no_auth_credentials.make_request(:get, '/foo', body, headers)
206
+ end
207
+
208
+ it 'returns a URI' do
209
+ uri = URI.parse(basic_client.options[:site])
210
+ expect(basic_client.uri).to eq(uri)
211
+ end
212
+
213
+ it 'sets up a http connection with options' do
214
+ http_conn = double
215
+ uri = double
216
+ host = double
217
+ port = double
218
+ expect(uri).to receive(:host).and_return(host)
219
+ expect(uri).to receive(:port).and_return(port)
220
+ expect(Net::HTTP).to receive(:new).with(host, port).and_return(http_conn)
221
+ expect(http_conn).to receive(:use_ssl=).with(basic_client.options[:use_ssl]).and_return(http_conn)
222
+ expect(http_conn).to receive(:verify_mode=).with(basic_client.options[:ssl_verify_mode]).and_return(http_conn)
223
+ expect(http_conn).to receive(:read_timeout=).with(basic_client.options[:read_timeout]).and_return(http_conn)
224
+ expect(basic_client.http_conn(uri)).to eq(http_conn)
225
+ end
226
+
227
+ it 'sets the SSL version when one is provided' do
228
+ http_conn = double
229
+ uri = double
230
+ host = double
231
+ port = double
232
+ expect(uri).to receive(:host).and_return(host)
233
+ expect(uri).to receive(:port).and_return(port)
234
+ expect(Net::HTTP).to receive(:new).with(host, port).and_return(http_conn)
235
+ expect(http_conn).to receive(:use_ssl=).with(basic_client.options[:use_ssl]).and_return(http_conn)
236
+ expect(http_conn).to receive(:verify_mode=).with(basic_client.options[:ssl_verify_mode]).and_return(http_conn)
237
+ expect(http_conn).to receive(:ssl_version=).with(custom_ssl_version_client.options[:ssl_version]).and_return(http_conn)
238
+ expect(http_conn).to receive(:read_timeout=).with(basic_client.options[:read_timeout]).and_return(http_conn)
239
+ expect(custom_ssl_version_client.http_conn(uri)).to eq(http_conn)
240
+ end
241
+
242
+ it 'sets up a non-proxied http connection by default' do
243
+ uri = double
244
+ host = double
245
+ port = double
246
+
247
+ expect(uri).to receive(:host).and_return(host)
248
+ expect(uri).to receive(:port).and_return(port)
249
+
250
+ proxy_configuration = basic_client.http_conn(uri).class
251
+ expect(proxy_configuration.proxy_address).to be_nil
252
+ expect(proxy_configuration.proxy_port).to be_nil
253
+ expect(proxy_configuration.proxy_user).to be_nil
254
+ expect(proxy_configuration.proxy_pass).to be_nil
255
+ end
256
+
257
+ it 'sets up a proxied http connection when using proxy options' do
258
+ uri = double
259
+ host = double
260
+ port = double
261
+
262
+ expect(uri).to receive(:host).and_return(host)
263
+ expect(uri).to receive(:port).and_return(port)
264
+
265
+ proxy_configuration = proxy_client.http_conn(uri).class
266
+ expect(proxy_configuration.proxy_address).to eq(proxy_client.options[:proxy_address])
267
+ expect(proxy_configuration.proxy_port).to eq(proxy_client.options[:proxy_port])
268
+ expect(proxy_configuration.proxy_user).to eq(proxy_client.options[:proxy_username])
269
+ expect(proxy_configuration.proxy_pass).to eq(proxy_client.options[:proxy_password])
270
+ end
271
+
272
+ it 'can use client certificates' do
273
+ http_conn = double
274
+ uri = double
275
+ host = double
276
+ port = double
277
+ expect(Net::HTTP).to receive(:new).with(host, port).and_return(http_conn)
278
+ expect(uri).to receive(:host).and_return(host)
279
+ expect(uri).to receive(:port).and_return(port)
280
+ expect(http_conn).to receive(:use_ssl=).with(basic_client.options[:use_ssl])
281
+ expect(http_conn).to receive(:verify_mode=).with(basic_client.options[:ssl_verify_mode])
282
+ expect(http_conn).to receive(:read_timeout=).with(basic_client.options[:read_timeout])
283
+ expect(http_conn).to receive(:cert=).with(basic_client_cert_client.options[:ssl_client_cert])
284
+ expect(http_conn).to receive(:key=).with(basic_client_cert_client.options[:ssl_client_key])
285
+ expect(basic_client_cert_client.http_conn(uri)).to eq(http_conn)
286
+ end
287
+
288
+ it 'returns a http connection' do
289
+ http_conn = double
290
+ uri = double
291
+ expect(basic_client).to receive(:uri).and_return(uri)
292
+ expect(basic_client).to receive(:http_conn).and_return(http_conn)
293
+ expect(basic_client.basic_auth_http_conn).to eq(http_conn)
294
+ end
295
+
296
+ describe '#make_multipart_request' do
297
+ subject do
298
+ basic_client.make_multipart_request(path, data, headers)
299
+ end
300
+
301
+ let(:path) { '/foo' }
302
+ let(:data) { {} }
303
+ let(:headers) { { 'X-Atlassian-Token' => 'no-check' } }
304
+ let(:basic_auth_http_conn) { double }
305
+ let(:request) { double('Http Request', path: path) }
306
+ let(:response) { double('response') }
307
+
308
+ before do
309
+ allow(request).to receive(:basic_auth)
310
+ allow(Net::HTTP::Post::Multipart).to receive(:new).with(path, data, headers).and_return(request)
311
+ allow(basic_client).to receive(:basic_auth_http_conn).and_return(basic_auth_http_conn)
312
+ allow(basic_auth_http_conn).to receive(:request).with(request).and_return(response)
313
+ end
314
+
315
+ it 'performs a basic http client request' do
316
+ expect(request).to receive(:basic_auth).with(basic_client.options[:username], basic_client.options[:password]).and_return(request)
317
+
318
+ subject
319
+ end
320
+
321
+ it 'makes a correct HTTP request' do
322
+ expect(basic_auth_http_conn).to receive(:request).with(request).and_return(response)
323
+ expect(response).to receive(:is_a?).with(Net::HTTPOK)
324
+
325
+ subject
326
+ end
327
+ end
328
+ end