sclemmer-jira-ruby 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +46 -0
  6. data/README.rdoc +309 -0
  7. data/Rakefile +28 -0
  8. data/example.rb +119 -0
  9. data/http-basic-example.rb +112 -0
  10. data/lib/jira.rb +33 -0
  11. data/lib/jira/base.rb +497 -0
  12. data/lib/jira/base_factory.rb +49 -0
  13. data/lib/jira/client.rb +165 -0
  14. data/lib/jira/has_many_proxy.rb +43 -0
  15. data/lib/jira/http_client.rb +69 -0
  16. data/lib/jira/http_error.rb +16 -0
  17. data/lib/jira/oauth_client.rb +84 -0
  18. data/lib/jira/railtie.rb +10 -0
  19. data/lib/jira/request_client.rb +18 -0
  20. data/lib/jira/resource/attachment.rb +12 -0
  21. data/lib/jira/resource/comment.rb +14 -0
  22. data/lib/jira/resource/component.rb +10 -0
  23. data/lib/jira/resource/field.rb +10 -0
  24. data/lib/jira/resource/filter.rb +15 -0
  25. data/lib/jira/resource/issue.rb +80 -0
  26. data/lib/jira/resource/issuetype.rb +10 -0
  27. data/lib/jira/resource/priority.rb +10 -0
  28. data/lib/jira/resource/project.rb +31 -0
  29. data/lib/jira/resource/status.rb +10 -0
  30. data/lib/jira/resource/transition.rb +33 -0
  31. data/lib/jira/resource/user.rb +14 -0
  32. data/lib/jira/resource/version.rb +10 -0
  33. data/lib/jira/resource/worklog.rb +16 -0
  34. data/lib/jira/tasks.rb +0 -0
  35. data/lib/jira/version.rb +3 -0
  36. data/lib/tasks/generate.rake +18 -0
  37. data/sclemmer-jira-ruby.gemspec +28 -0
  38. data/spec/integration/attachment_spec.rb +23 -0
  39. data/spec/integration/comment_spec.rb +55 -0
  40. data/spec/integration/component_spec.rb +42 -0
  41. data/spec/integration/field_spec.rb +35 -0
  42. data/spec/integration/issue_spec.rb +94 -0
  43. data/spec/integration/issuetype_spec.rb +26 -0
  44. data/spec/integration/priority_spec.rb +27 -0
  45. data/spec/integration/project_spec.rb +56 -0
  46. data/spec/integration/status_spec.rb +27 -0
  47. data/spec/integration/transition_spec.rb +52 -0
  48. data/spec/integration/user_spec.rb +25 -0
  49. data/spec/integration/version_spec.rb +43 -0
  50. data/spec/integration/worklog_spec.rb +55 -0
  51. data/spec/jira/base_factory_spec.rb +46 -0
  52. data/spec/jira/base_spec.rb +583 -0
  53. data/spec/jira/client_spec.rb +188 -0
  54. data/spec/jira/has_many_proxy_spec.rb +47 -0
  55. data/spec/jira/http_client_spec.rb +109 -0
  56. data/spec/jira/http_error_spec.rb +26 -0
  57. data/spec/jira/oauth_client_spec.rb +111 -0
  58. data/spec/jira/request_client_spec.rb +14 -0
  59. data/spec/jira/resource/attachment_spec.rb +20 -0
  60. data/spec/jira/resource/filter_spec.rb +97 -0
  61. data/spec/jira/resource/issue_spec.rb +123 -0
  62. data/spec/jira/resource/project_factory_spec.rb +13 -0
  63. data/spec/jira/resource/project_spec.rb +70 -0
  64. data/spec/jira/resource/worklog_spec.rb +24 -0
  65. data/spec/mock_responses/attachment/10000.json +20 -0
  66. data/spec/mock_responses/component.post.json +28 -0
  67. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  68. data/spec/mock_responses/component/10000.json +39 -0
  69. data/spec/mock_responses/component/10000.put.json +39 -0
  70. data/spec/mock_responses/field.json +32 -0
  71. data/spec/mock_responses/field/1.json +15 -0
  72. data/spec/mock_responses/issue.json +1108 -0
  73. data/spec/mock_responses/issue.post.json +5 -0
  74. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  75. data/spec/mock_responses/issue/10002.json +126 -0
  76. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  77. data/spec/mock_responses/issue/10002/comment.json +65 -0
  78. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  79. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  80. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  81. data/spec/mock_responses/issue/10002/transitions.json +49 -0
  82. data/spec/mock_responses/issue/10002/transitions.post.json +1 -0
  83. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  84. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  85. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  86. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  87. data/spec/mock_responses/issuetype.json +42 -0
  88. data/spec/mock_responses/issuetype/5.json +8 -0
  89. data/spec/mock_responses/priority.json +42 -0
  90. data/spec/mock_responses/priority/1.json +8 -0
  91. data/spec/mock_responses/project.json +12 -0
  92. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  93. data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
  94. data/spec/mock_responses/status.json +37 -0
  95. data/spec/mock_responses/status/1.json +7 -0
  96. data/spec/mock_responses/user_username=admin.json +17 -0
  97. data/spec/mock_responses/version.post.json +7 -0
  98. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  99. data/spec/mock_responses/version/10000.json +11 -0
  100. data/spec/mock_responses/version/10000.put.json +7 -0
  101. data/spec/spec_helper.rb +22 -0
  102. data/spec/support/clients_helper.rb +16 -0
  103. data/spec/support/matchers/have_attributes.rb +11 -0
  104. data/spec/support/matchers/have_many.rb +9 -0
  105. data/spec/support/matchers/have_one.rb +5 -0
  106. data/spec/support/shared_examples/integration.rb +194 -0
  107. metadata +302 -0
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::BaseFactory do
4
+
5
+ class JIRA::Resource::FooFactory < JIRA::BaseFactory ; end
6
+ class JIRA::Resource::Foo ; end
7
+
8
+ let(:client) { double() }
9
+ subject { JIRA::Resource::FooFactory.new(client) }
10
+
11
+ it "initializes correctly" do
12
+ expect(subject.class).to eq(JIRA::Resource::FooFactory)
13
+ expect(subject.client).to eq(client)
14
+ expect(subject.target_class).to eq(JIRA::Resource::Foo)
15
+ end
16
+
17
+ it "proxies all to the target class" do
18
+ expect(JIRA::Resource::Foo).to receive(:all).with(client)
19
+ subject.all
20
+ end
21
+
22
+ it "proxies find to the target class" do
23
+ expect(JIRA::Resource::Foo).to receive(:find).with(client, 'FOO')
24
+ subject.find('FOO')
25
+ end
26
+
27
+ it "returns the target class" do
28
+ expect(subject.target_class).to eq(JIRA::Resource::Foo)
29
+ end
30
+
31
+ it "proxies build to the target class" do
32
+ attrs = double()
33
+ expect(JIRA::Resource::Foo).to receive(:build).with(client, attrs)
34
+ subject.build(attrs)
35
+ end
36
+
37
+ it "proxies collection path to the target class" do
38
+ expect(JIRA::Resource::Foo).to receive(:collection_path).with(client)
39
+ subject.collection_path
40
+ end
41
+
42
+ it "proxies singular path to the target class" do
43
+ expect(JIRA::Resource::Foo).to receive(:singular_path).with(client, 'FOO')
44
+ subject.singular_path('FOO')
45
+ end
46
+ end
@@ -0,0 +1,583 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Base do
4
+
5
+ class JIRA::Resource::Deadbeef < JIRA::Base # :nodoc:
6
+ end
7
+
8
+ class JIRA::Resource::HasOneExample < JIRA::Base # :nodoc:
9
+ has_one :deadbeef
10
+ has_one :muffin, :class => JIRA::Resource::Deadbeef
11
+ has_one :brunchmuffin, :class => JIRA::Resource::Deadbeef,
12
+ :nested_under => 'nested'
13
+ has_one :breakfastscone,
14
+ :class => JIRA::Resource::Deadbeef,
15
+ :nested_under => ['nested','breakfastscone']
16
+ has_one :irregularly_named_thing,
17
+ :class => JIRA::Resource::Deadbeef,
18
+ :attribute_key => 'irregularlyNamedThing'
19
+ end
20
+
21
+ class JIRA::Resource::HasManyExample < JIRA::Base # :nodoc:
22
+ has_many :deadbeefs
23
+ has_many :brunchmuffins, :class => JIRA::Resource::Deadbeef,
24
+ :nested_under => 'nested'
25
+ has_many :breakfastscones,
26
+ :class => JIRA::Resource::Deadbeef,
27
+ :nested_under => ['nested','breakfastscone']
28
+ has_many :irregularly_named_things,
29
+ :class => JIRA::Resource::Deadbeef,
30
+ :attribute_key => 'irregularlyNamedThings'
31
+
32
+ end
33
+
34
+ let(:client) { double("client") }
35
+ let(:attrs) { Hash.new }
36
+
37
+ subject { JIRA::Resource::Deadbeef.new(client, :attrs => attrs) }
38
+
39
+ it "assigns the client and attrs" do
40
+ expect(subject.client).to eq(client)
41
+ expect(subject.attrs).to eq(attrs)
42
+ end
43
+
44
+ it "returns all the deadbeefs" do
45
+ response = double()
46
+ expect(response).to receive(:body).and_return('[{"self":"http://deadbeef/","id":"98765"}]')
47
+ expect(client).to receive(:get).with('/jira/rest/api/2/deadbeef').and_return(response)
48
+ expect(JIRA::Resource::Deadbeef).to receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
49
+ deadbeefs = JIRA::Resource::Deadbeef.all(client)
50
+ expect(deadbeefs.length).to eq(1)
51
+ first = deadbeefs.first
52
+ expect(first.class).to eq(JIRA::Resource::Deadbeef)
53
+ expect(first.attrs['self']).to eq('http://deadbeef/')
54
+ expect(first.attrs['id']).to eq('98765')
55
+ expect(first.expanded?).to be_falsey
56
+ end
57
+
58
+ it "finds a deadbeef by id" do
59
+ response = instance_double("Response", body: '{"self":"http://deadbeef/","id":"98765"}')
60
+ expect(client).to receive(:get).with('/jira/rest/api/2/deadbeef/98765').and_return(response)
61
+ expect(JIRA::Resource::Deadbeef).to receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
62
+ deadbeef = JIRA::Resource::Deadbeef.find(client, '98765')
63
+ expect(deadbeef.client).to eq(client)
64
+ expect(deadbeef.attrs['self']).to eq('http://deadbeef/')
65
+ expect(deadbeef.attrs['id']).to eq('98765')
66
+ expect(deadbeef.expanded?).to be_truthy
67
+ end
68
+
69
+ it "finds a deadbeef containing changelog by id" do
70
+ response = instance_double(
71
+ "Response",
72
+ body: '{"self":"http://deadbeef/","id":"98765","changelog":{"histories":[]}}'
73
+ )
74
+ expect(client).to receive(:get).with('/jira/rest/api/2/deadbeef/98765?expand=changelog').and_return(response)
75
+
76
+ expect(JIRA::Resource::Deadbeef).to receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
77
+
78
+ deadbeef = JIRA::Resource::Deadbeef.find(client, '98765', {expand:'changelog'})
79
+ expect(deadbeef.client).to eq(client)
80
+ expect(deadbeef.attrs['self']).to eq('http://deadbeef/')
81
+ expect(deadbeef.attrs['id']).to eq('98765')
82
+ expect(deadbeef.expanded?).to be_truthy
83
+ expect(deadbeef.attrs['changelog']['histories']).to eq([])
84
+ end
85
+
86
+ it "builds a deadbeef" do
87
+ deadbeef = JIRA::Resource::Deadbeef.build(client, 'id' => "98765" )
88
+ expect(deadbeef.expanded?).to be_falsey
89
+
90
+ expect(deadbeef.client).to eq(client)
91
+ expect(deadbeef.attrs['id']).to eq('98765')
92
+ end
93
+
94
+ it "returns the endpoint name" do
95
+ expect(subject.class.endpoint_name).to eq('deadbeef')
96
+ end
97
+
98
+ it "returns the path_component" do
99
+ attrs['id'] = '123'
100
+ expect(subject.path_component).to eq('/deadbeef/123')
101
+ end
102
+
103
+ it "returns the path component for unsaved instances" do
104
+ expect(subject.path_component).to eq('/deadbeef')
105
+ end
106
+
107
+ it "converts to a symbol" do
108
+ expect(subject.to_sym).to eq(:deadbeef)
109
+ end
110
+
111
+ describe "collection_path" do
112
+
113
+ before(:each) do
114
+ expect(client).to receive(:options).and_return(:rest_base_path => '/deadbeef/bar')
115
+ end
116
+
117
+ it "returns the collection_path" do
118
+ expect(subject.collection_path).to eq('/deadbeef/bar/deadbeef')
119
+ end
120
+
121
+ it "returns the collection_path with a prefix" do
122
+ expect(subject.collection_path('/baz/')).to eq('/deadbeef/bar/baz/deadbeef')
123
+ end
124
+
125
+ it "has a class method that returns the collection_path" do
126
+ expect(subject.class.collection_path(client)).to eq('/deadbeef/bar/deadbeef')
127
+ end
128
+ end
129
+
130
+ it "parses json" do
131
+ expect(described_class.parse_json('{"foo":"bar"}')).to eq({"foo" => "bar"})
132
+ end
133
+
134
+ describe "dynamic instance methods" do
135
+
136
+ let(:attrs) { {'foo' => 'bar', 'flum' => 'goo', 'object_id' => 'dummy'} }
137
+ subject { JIRA::Resource::Deadbeef.new(client, :attrs => attrs) }
138
+
139
+ it "responds to each of the top level attribute names" do
140
+ expect(subject).to respond_to(:foo)
141
+ expect(subject).to respond_to('flum')
142
+ expect(subject).to respond_to(:object_id)
143
+
144
+ expect(subject.foo).to eq('bar')
145
+ expect(subject.flum).to eq('goo')
146
+
147
+ # Should not override existing method names, but should still allow
148
+ # access to their values via the attrs[] hash
149
+ expect(subject.object_id).not_to eq('dummy')
150
+ expect(subject.attrs['object_id']).to eq('dummy')
151
+ end
152
+ end
153
+
154
+ describe "fetch" do
155
+
156
+ subject { JIRA::Resource::Deadbeef.new(client, :attrs => {'id' => '98765'}) }
157
+
158
+ describe "not cached" do
159
+
160
+ before(:each) do
161
+ response = instance_double("Response", body: '{"self":"http://deadbeef/","id":"98765"}')
162
+ expect(client).to receive(:get).with('/jira/rest/api/2/deadbeef/98765').and_return(response)
163
+ expect(JIRA::Resource::Deadbeef).to receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
164
+ end
165
+
166
+ it "sets expanded to true after fetch" do
167
+ expect(subject.expanded?).to be_falsey
168
+ subject.fetch
169
+ expect(subject.expanded?).to be_truthy
170
+ end
171
+
172
+ it "performs a fetch" do
173
+ expect(subject.expanded?).to be_falsey
174
+ subject.fetch
175
+ expect(subject.self).to eq("http://deadbeef/")
176
+ expect(subject.id).to eq("98765")
177
+ end
178
+
179
+ it "performs a fetch if already fetched and force flag is true" do
180
+ subject.expanded = true
181
+ subject.fetch(true)
182
+ end
183
+
184
+ end
185
+
186
+ describe "cached" do
187
+ it "doesn't perform a fetch if already fetched" do
188
+ subject.expanded = true
189
+ expect(client).not_to receive(:get)
190
+ subject.fetch
191
+ end
192
+ end
193
+
194
+ context "with expand parameter 'changelog'" do
195
+ it "fetchs changelogs '" do
196
+ response = instance_double(
197
+ "Response",
198
+ body: '{"self":"http://deadbeef/","id":"98765","changelog":{"histories":[]}}'
199
+ )
200
+ expect(client).to receive(:get).with('/jira/rest/api/2/deadbeef/98765?expand=changelog').and_return(response)
201
+
202
+ expect(JIRA::Resource::Deadbeef).to receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
203
+
204
+ subject.fetch(false, {expand:'changelog'})
205
+
206
+ expect(subject.self).to eq("http://deadbeef/")
207
+ expect(subject.id).to eq("98765")
208
+ expect(subject.changelog['histories']).to eq([])
209
+ end
210
+ end
211
+ end
212
+
213
+ describe "save" do
214
+
215
+ let(:response) { double() }
216
+
217
+ subject { JIRA::Resource::Deadbeef.new(client) }
218
+
219
+ before(:each) do
220
+ expect(subject).to receive(:url).and_return('/foo/bar')
221
+ end
222
+
223
+ it "POSTs a new record" do
224
+ response = instance_double("Response", body: '{"id":"123"}')
225
+ allow(subject).to receive(:new_record?) { true }
226
+ expect(client).to receive(:post).with('/foo/bar','{"foo":"bar"}').and_return(response)
227
+ expect(subject.save("foo" => "bar")).to be_truthy
228
+ expect(subject.id).to eq("123")
229
+ expect(subject.expanded).to be_falsey
230
+ end
231
+
232
+ it "PUTs an existing record" do
233
+ response = instance_double("Response", body: nil)
234
+ allow(subject).to receive(:new_record?) { false }
235
+ expect(client).to receive(:put).with('/foo/bar','{"foo":"bar"}').and_return(response)
236
+ expect(subject.save("foo" => "bar")).to be_truthy
237
+ expect(subject.expanded).to be_falsey
238
+ end
239
+
240
+ it "merges attrs on save" do
241
+ response = instance_double("Response", body: nil)
242
+ expect(client).to receive(:post).with('/foo/bar','{"foo":{"fum":"dum"}}').and_return(response)
243
+ subject.attrs = {"foo" => {"bar" => "baz"}}
244
+ subject.save({"foo" => {"fum" => "dum"}})
245
+ expect(subject.foo).to eq({"bar" => "baz", "fum" => "dum"})
246
+ end
247
+
248
+ it "returns false when an invalid field is set" do # The JIRA REST API apparently ignores fields that you aren't allowed to set manually
249
+ response = instance_double("Response", body: '{"errorMessages":["blah"]}', status: 400)
250
+ allow(subject).to receive(:new_record?) { false }
251
+ expect(client).to receive(:put).with('/foo/bar','{"invalid_field":"foobar"}').and_raise(JIRA::HTTPError.new(response))
252
+ expect(subject.save("invalid_field" => "foobar")).to be_falsey
253
+ end
254
+
255
+ end
256
+
257
+ describe "save!" do
258
+ let(:response) { double() }
259
+
260
+ subject { JIRA::Resource::Deadbeef.new(client) }
261
+
262
+ before(:each) do
263
+ expect(subject).to receive(:url).and_return('/foo/bar')
264
+ end
265
+
266
+ it "POSTs a new record" do
267
+ response = instance_double("Response", body: '{"id":"123"}')
268
+ allow(subject).to receive(:new_record?) { true }
269
+ expect(client).to receive(:post).with('/foo/bar','{"foo":"bar"}').and_return(response)
270
+ expect(subject.save!("foo" => "bar")).to be_truthy
271
+ expect(subject.id).to eq("123")
272
+ expect(subject.expanded).to be_falsey
273
+ end
274
+
275
+ it "PUTs an existing record" do
276
+ response = instance_double("Response", body: nil)
277
+ allow(subject).to receive(:new_record?) { false }
278
+ expect(client).to receive(:put).with('/foo/bar','{"foo":"bar"}').and_return(response)
279
+ expect(subject.save!("foo" => "bar")).to be_truthy
280
+ expect(subject.expanded).to be_falsey
281
+ end
282
+
283
+ it "throws an exception when an invalid field is set" do
284
+ response = instance_double("Response", body: '{"errorMessages":["blah"]}', status: 400)
285
+ allow(subject).to receive(:new_record?) { false }
286
+ expect(client).to receive(:put).with('/foo/bar','{"invalid_field":"foobar"}').and_raise(JIRA::HTTPError.new(response))
287
+ expect(lambda{ subject.save!("invalid_field" => "foobar") }).to raise_error(JIRA::HTTPError)
288
+ end
289
+ end
290
+
291
+ describe "set_attrs" do
292
+ it "merges hashes correctly when clobber is true (default)" do
293
+ subject.attrs = {"foo" => {"bar" => "baz"}}
294
+ subject.set_attrs({"foo" => {"fum" => "dum"}})
295
+ expect(subject.foo).to eq({"fum" => "dum"})
296
+ end
297
+
298
+ it "merges hashes correctly when clobber is false" do
299
+ subject.attrs = {"foo" => {"bar" => "baz"}}
300
+ subject.set_attrs({"foo" => {"fum" => "dum"}}, false)
301
+ expect(subject.foo).to eq({"bar" => "baz", "fum" => "dum"})
302
+ end
303
+ end
304
+
305
+ describe "delete" do
306
+
307
+ before(:each) do
308
+ expect(client).to receive(:delete).with('/foo/bar')
309
+ allow(subject).to receive(:url) { '/foo/bar' }
310
+ end
311
+
312
+ it "flags itself as deleted" do
313
+ expect(subject.deleted?).to be_falsey
314
+ subject.delete
315
+ expect(subject.deleted?).to be_truthy
316
+ end
317
+
318
+ it "sends a DELETE request" do
319
+ subject.delete
320
+ end
321
+
322
+ end
323
+
324
+ describe "new_record?" do
325
+
326
+ it "returns true for new_record? when new object" do
327
+ subject.attrs['id'] = nil
328
+ expect(subject.new_record?).to be_truthy
329
+ end
330
+
331
+ it "returns false for new_record? when id is set" do
332
+ subject.attrs['id'] = '123'
333
+ expect(subject.new_record?).to be_falsey
334
+ end
335
+
336
+ end
337
+
338
+ describe "has_errors?" do
339
+
340
+ it "returns true when the response contains errors" do
341
+ attrs["errors"] = {"invalid" => "Field invalid"}
342
+ expect(subject.has_errors?).to be_truthy
343
+ end
344
+
345
+ it "returns false when the response does not contain any errors" do
346
+ expect(subject.has_errors?).to be_falsey
347
+ end
348
+
349
+ end
350
+
351
+ describe 'url' do
352
+
353
+ before(:each) do
354
+ allow(client).to receive(:options) { {:rest_base_path => '/foo/bar'} }
355
+ end
356
+
357
+ it "returns self as the URL if set" do
358
+ pending("Identified bug on real jira instance")
359
+ attrs['self'] = 'http://foo/bar'
360
+ expect(subject.url).to eq("http://foo/bar")
361
+ end
362
+
363
+ it "generates the URL from id if self not set" do
364
+ attrs['self'] = nil
365
+ attrs['id'] = '98765'
366
+ expect(subject.url).to eq("/foo/bar/deadbeef/98765")
367
+ end
368
+
369
+ it "generates the URL from collection_path if self and id not set" do
370
+ attrs['self'] = nil
371
+ attrs['id'] = nil
372
+ expect(subject.url).to eq("/foo/bar/deadbeef")
373
+ end
374
+
375
+ it "has a class method for the collection path" do
376
+ expect(JIRA::Resource::Deadbeef.collection_path(client)).to eq("/foo/bar/deadbeef")
377
+ #Should accept an optional prefix (flum in this case)
378
+ expect(JIRA::Resource::Deadbeef.collection_path(client, '/flum/')).to eq("/foo/bar/flum/deadbeef")
379
+ end
380
+
381
+ it "has a class method for the singular path" do
382
+ expect(JIRA::Resource::Deadbeef.singular_path(client, 'abc123')).to eq("/foo/bar/deadbeef/abc123")
383
+ #Should accept an optional prefix (flum in this case)
384
+ expect(JIRA::Resource::Deadbeef.singular_path(client, 'abc123', '/flum/')).to eq("/foo/bar/flum/deadbeef/abc123")
385
+ end
386
+ end
387
+
388
+ it "returns the formatted attrs from to_s" do
389
+ subject.attrs['foo'] = 'bar'
390
+ subject.attrs['dead'] = 'beef'
391
+
392
+ expect(subject.to_s).to match(/#<JIRA::Resource::Deadbeef:\d+ @attrs=#{Regexp.quote(attrs.inspect)}>/)
393
+ end
394
+
395
+ it "returns the key attribute" do
396
+ expect(subject.class.key_attribute).to eq(:id)
397
+ end
398
+
399
+ it "returns the key value" do
400
+ subject.attrs['id'] = '123'
401
+ expect(subject.key_value).to eq('123')
402
+ end
403
+
404
+ it "converts to json" do
405
+ subject.attrs = {"foo" => "bar","dead" => "beef"}
406
+
407
+ expect(subject.to_json).to eq(subject.attrs.to_json)
408
+ end
409
+
410
+ describe "extract attrs from response" do
411
+
412
+ subject { JIRA::Resource::Deadbeef.new(client, :attrs => {}) }
413
+
414
+ it "sets the attrs from a response" do
415
+ response = instance_double("Response", body: '{"foo":"bar"}')
416
+
417
+ expect(subject.set_attrs_from_response(response)).to eq({'foo' => 'bar'})
418
+ expect(subject.foo).to eq("bar")
419
+ end
420
+
421
+ it "doesn't clobber existing attrs not in response" do
422
+ response = instance_double("Response", body: '{"foo":"bar"}')
423
+
424
+ subject.attrs = {'flum' => 'flar'}
425
+ expect(subject.set_attrs_from_response(response)).to eq({'foo' => 'bar'})
426
+ expect(subject.foo).to eq("bar")
427
+ expect(subject.flum).to eq("flar")
428
+ end
429
+
430
+ it "handles nil response body" do
431
+ response = instance_double("Response", body: nil)
432
+
433
+ subject.attrs = {'flum' => 'flar'}
434
+ expect(subject.set_attrs_from_response(response)).to be_nil
435
+ expect(subject.flum).to eq('flar')
436
+ end
437
+ end
438
+
439
+ describe "nesting" do
440
+
441
+ it "defaults collection_attributes_are_nested to false" do
442
+ expect(JIRA::Resource::Deadbeef.collection_attributes_are_nested).to be_falsey
443
+ end
444
+
445
+ it "allows collection_attributes_are_nested to be set" do
446
+ JIRA::Resource::Deadbeef.nested_collections true
447
+ expect(JIRA::Resource::Deadbeef.collection_attributes_are_nested).to be_truthy
448
+ end
449
+
450
+ end
451
+
452
+ describe "has_many" do
453
+
454
+ subject { JIRA::Resource::HasManyExample.new(client, :attrs => {'deadbeefs' => [{'id' => '123'}]}) }
455
+
456
+ it "returns a collection of instances for has_many relationships" do
457
+ expect(subject.deadbeefs.class).to eq(JIRA::HasManyProxy)
458
+ expect(subject.deadbeefs.length).to eq(1)
459
+ subject.deadbeefs.each do |deadbeef|
460
+ expect(deadbeef.class).to eq(JIRA::Resource::Deadbeef)
461
+ end
462
+ end
463
+
464
+ it "returns an empty collection for empty has_many relationships" do
465
+ subject = JIRA::Resource::HasManyExample.new(client)
466
+ expect(subject.deadbeefs.length).to eq(0)
467
+ end
468
+
469
+ it "allows the has_many attributes to be nested inside another attribute" do
470
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {'nested' => {'brunchmuffins' => [{'id' => '123'},{'id' => '456'}]}})
471
+ expect(subject.brunchmuffins.length).to eq(2)
472
+ subject.brunchmuffins.each do |brunchmuffin|
473
+ expect(brunchmuffin.class).to eq(JIRA::Resource::Deadbeef)
474
+ end
475
+ end
476
+
477
+ it "allows it to be deeply nested" do
478
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {'nested' => {
479
+ 'breakfastscone' => { 'breakfastscones' => [{'id' => '123'},{'id' => '456'}] }
480
+ }})
481
+ expect(subject.breakfastscones.length).to eq(2)
482
+ subject.breakfastscones.each do |breakfastscone|
483
+ expect(breakfastscone.class).to eq(JIRA::Resource::Deadbeef)
484
+ end
485
+ end
486
+
487
+ it "short circuits missing deeply nested attrs" do
488
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {
489
+ 'nested' => {}
490
+ })
491
+ expect(subject.breakfastscones.length).to eq(0)
492
+ end
493
+
494
+ it "allows the attribute key to be specified" do
495
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {'irregularlyNamedThings' => [{'id' => '123'},{'id' => '456'}]})
496
+ expect(subject.irregularly_named_things.length).to eq(2)
497
+ subject.irregularly_named_things.each do |thing|
498
+ expect(thing.class).to eq(JIRA::Resource::Deadbeef)
499
+ end
500
+ end
501
+
502
+ it "can build child instances" do
503
+ deadbeef = subject.deadbeefs.build
504
+ expect(deadbeef.class).to eq(JIRA::Resource::Deadbeef)
505
+ end
506
+
507
+ end
508
+
509
+ describe "has_one" do
510
+
511
+ subject { JIRA::Resource::HasOneExample.new(client, :attrs => {'deadbeef' => {'id' => '123'}}) }
512
+
513
+ it "returns an instance for a has one relationship" do
514
+ expect(subject.deadbeef.class).to eq(JIRA::Resource::Deadbeef)
515
+ expect(subject.deadbeef.id).to eq('123')
516
+ end
517
+
518
+ it "returns nil when resource attribute is nonexistent" do
519
+ subject = JIRA::Resource::HasOneExample.new(client)
520
+ expect(subject.deadbeef).to be_nil
521
+ end
522
+
523
+ it "returns an instance with a different class name to the attribute name" do
524
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'muffin' => {'id' => '123'}})
525
+ expect(subject.muffin.class).to eq(JIRA::Resource::Deadbeef)
526
+ expect(subject.muffin.id).to eq('123')
527
+ end
528
+
529
+ it "allows the has_one attributes to be nested inside another attribute" do
530
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'nested' => {'brunchmuffin' => {'id' => '123'}}})
531
+ expect(subject.brunchmuffin.class).to eq(JIRA::Resource::Deadbeef)
532
+ expect(subject.brunchmuffin.id).to eq('123')
533
+ end
534
+
535
+ it "allows it to be deeply nested" do
536
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'nested' => {
537
+ 'breakfastscone' => { 'breakfastscone' => {'id' => '123'} }
538
+ }})
539
+ expect(subject.breakfastscone.class).to eq(JIRA::Resource::Deadbeef)
540
+ expect(subject.breakfastscone.id).to eq('123')
541
+ end
542
+
543
+ it "allows the attribute key to be specified" do
544
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'irregularlyNamedThing' => {'id' => '123'}})
545
+ expect(subject.irregularly_named_thing.class).to eq(JIRA::Resource::Deadbeef)
546
+ expect(subject.irregularly_named_thing.id).to eq('123')
547
+ end
548
+
549
+ end
550
+
551
+ describe "belongs_to" do
552
+
553
+ class JIRA::Resource::BelongsToExample < JIRA::Base
554
+ belongs_to :deadbeef
555
+ end
556
+
557
+ let(:deadbeef) { JIRA::Resource::Deadbeef.new(client, :attrs => {'id' => "999"}) }
558
+
559
+ subject { JIRA::Resource::BelongsToExample.new(client, :attrs => {'id' => '123'}, :deadbeef => deadbeef) }
560
+
561
+ it "sets up an accessor for the belongs to relationship" do
562
+ expect(subject.deadbeef).to eq(deadbeef)
563
+ end
564
+
565
+ it "raises an exception when initialized without a belongs_to instance" do
566
+ expect(lambda {
567
+ JIRA::Resource::BelongsToExample.new(client, :attrs => {'id' => '123'})
568
+ }).to raise_exception(ArgumentError,"Required option :deadbeef missing")
569
+ end
570
+
571
+ it "returns the right url" do
572
+ allow(client).to receive(:options) { { :rest_base_path => "/foo" } }
573
+ expect(subject.url).to eq("/foo/deadbeef/999/belongstoexample/123")
574
+ end
575
+
576
+ it "can be initialized with an instance or a key value" do
577
+ allow(client).to receive(:options) { { :rest_base_path => "/foo" } }
578
+ subject = JIRA::Resource::BelongsToExample.new(client, :attrs => {'id' => '123'}, :deadbeef_id => '987')
579
+ expect(subject.url).to eq("/foo/deadbeef/987/belongstoexample/123")
580
+ end
581
+
582
+ end
583
+ end