jira-ruby-dmg 0.1.10

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 (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/jira-ruby-dmg.gemspec +28 -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 +76 -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/jira.rb +33 -0
  37. data/lib/tasks/generate.rake +18 -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 +586 -0
  53. data/spec/jira/client_spec.rb +188 -0
  54. data/spec/jira/has_many_proxy_spec.rb +45 -0
  55. data/spec/jira/http_client_spec.rb +109 -0
  56. data/spec/jira/http_error_spec.rb +25 -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 +107 -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/10000.invalid.put.json +5 -0
  67. data/spec/mock_responses/component/10000.json +39 -0
  68. data/spec/mock_responses/component/10000.put.json +39 -0
  69. data/spec/mock_responses/component.post.json +28 -0
  70. data/spec/mock_responses/field/1.json +15 -0
  71. data/spec/mock_responses/field.json +32 -0
  72. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  73. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  74. data/spec/mock_responses/issue/10002/comment.json +65 -0
  75. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  76. data/spec/mock_responses/issue/10002/transitions.json +49 -0
  77. data/spec/mock_responses/issue/10002/transitions.post.json +1 -0
  78. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  79. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  80. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  81. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  82. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  83. data/spec/mock_responses/issue/10002.json +126 -0
  84. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  85. data/spec/mock_responses/issue.json +1108 -0
  86. data/spec/mock_responses/issue.post.json +5 -0
  87. data/spec/mock_responses/issuetype/5.json +8 -0
  88. data/spec/mock_responses/issuetype.json +42 -0
  89. data/spec/mock_responses/priority/1.json +8 -0
  90. data/spec/mock_responses/priority.json +42 -0
  91. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  92. data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
  93. data/spec/mock_responses/project.json +12 -0
  94. data/spec/mock_responses/status/1.json +7 -0
  95. data/spec/mock_responses/status.json +37 -0
  96. data/spec/mock_responses/user_username=admin.json +17 -0
  97. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  98. data/spec/mock_responses/version/10000.json +11 -0
  99. data/spec/mock_responses/version/10000.put.json +7 -0
  100. data/spec/mock_responses/version.post.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 +190 -0
  107. metadata +301 -0
@@ -0,0 +1,586 @@
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
+ subject.client.should == client
41
+ subject.attrs.should == attrs
42
+ end
43
+
44
+ it "returns all the deadbeefs" do
45
+ response = double()
46
+ response.should_receive(:body).and_return('[{"self":"http://deadbeef/","id":"98765"}]')
47
+ client.should_receive(:get).with('/jira/rest/api/2/deadbeef').and_return(response)
48
+ JIRA::Resource::Deadbeef.should_receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
49
+ deadbeefs = JIRA::Resource::Deadbeef.all(client)
50
+ deadbeefs.length.should == 1
51
+ first = deadbeefs.first
52
+ first.class.should == JIRA::Resource::Deadbeef
53
+ first.attrs['self'].should == 'http://deadbeef/'
54
+ first.attrs['id'].should == '98765'
55
+ first.expanded?.should be_false
56
+ end
57
+
58
+ it "finds a deadbeef by id" do
59
+ response = double()
60
+ response.stub(:body).and_return('{"self":"http://deadbeef/","id":"98765"}')
61
+ client.should_receive(:get).with('/jira/rest/api/2/deadbeef/98765').and_return(response)
62
+ JIRA::Resource::Deadbeef.should_receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
63
+ deadbeef = JIRA::Resource::Deadbeef.find(client, '98765')
64
+ deadbeef.client.should == client
65
+ deadbeef.attrs['self'].should == 'http://deadbeef/'
66
+ deadbeef.attrs['id'].should == '98765'
67
+ deadbeef.expanded?.should be_true
68
+ end
69
+
70
+ it "finds a deadbeef containing changelog by id" do
71
+ response = double()
72
+ response.stub(:body).and_return('{"self":"http://deadbeef/","id":"98765","changelog":{"histories":[]}}')
73
+ client.should_receive(:get).with('/jira/rest/api/2/deadbeef/98765?expand=changelog').and_return(response)
74
+
75
+ JIRA::Resource::Deadbeef.should_receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
76
+
77
+ deadbeef = JIRA::Resource::Deadbeef.find(client, '98765', {expand:'changelog'})
78
+ deadbeef.client.should == client
79
+ deadbeef.attrs['self'].should == 'http://deadbeef/'
80
+ deadbeef.attrs['id'].should == '98765'
81
+ deadbeef.expanded?.should be_true
82
+ deadbeef.attrs['changelog']['histories'].should == []
83
+ end
84
+
85
+ it "builds a deadbeef" do
86
+ deadbeef = JIRA::Resource::Deadbeef.build(client, 'id' => "98765" )
87
+ deadbeef.expanded?.should be_false
88
+
89
+ deadbeef.client.should == client
90
+ deadbeef.attrs['id'].should == '98765'
91
+ end
92
+
93
+ it "returns the endpoint name" do
94
+ subject.class.endpoint_name.should == 'deadbeef'
95
+ end
96
+
97
+ it "returns the path_component" do
98
+ attrs['id'] = '123'
99
+ subject.path_component.should == '/deadbeef/123'
100
+ end
101
+
102
+ it "returns the path component for unsaved instances" do
103
+ subject.path_component.should == '/deadbeef'
104
+ end
105
+
106
+ it "converts to a symbol" do
107
+ subject.to_sym.should == :deadbeef
108
+ end
109
+
110
+ describe "collection_path" do
111
+
112
+ before(:each) do
113
+ client.should_receive(:options).and_return(:rest_base_path => '/deadbeef/bar')
114
+ end
115
+
116
+ it "returns the collection_path" do
117
+ subject.collection_path.should == '/deadbeef/bar/deadbeef'
118
+ end
119
+
120
+ it "returns the collection_path with a prefix" do
121
+ subject.collection_path('/baz/').should == '/deadbeef/bar/baz/deadbeef'
122
+ end
123
+
124
+ it "has a class method that returns the collection_path" do
125
+ subject.class.collection_path(client).should == '/deadbeef/bar/deadbeef'
126
+ end
127
+ end
128
+
129
+ it "parses json" do
130
+ described_class.parse_json('{"foo":"bar"}').should == {"foo" => "bar"}
131
+ end
132
+
133
+ describe "dynamic instance methods" do
134
+
135
+ let(:attrs) { {'foo' => 'bar', 'flum' => 'goo', 'object_id' => 'dummy'} }
136
+ subject { JIRA::Resource::Deadbeef.new(client, :attrs => attrs) }
137
+
138
+ it "responds to each of the top level attribute names" do
139
+ subject.should respond_to(:foo)
140
+ subject.should respond_to('flum')
141
+ subject.should respond_to(:object_id)
142
+
143
+ subject.foo.should == 'bar'
144
+ subject.flum.should == 'goo'
145
+
146
+ # Should not override existing method names, but should still allow
147
+ # access to their values via the attrs[] hash
148
+ subject.object_id.should_not == 'dummy'
149
+ subject.attrs['object_id'].should == 'dummy'
150
+ end
151
+ end
152
+
153
+ describe "fetch" do
154
+
155
+ subject { JIRA::Resource::Deadbeef.new(client, :attrs => {'id' => '98765'}) }
156
+
157
+ describe "not cached" do
158
+
159
+ before(:each) do
160
+ response = double()
161
+ response.stub(:body).and_return('{"self":"http://deadbeef/","id":"98765"}')
162
+ client.should_receive(:get).with('/jira/rest/api/2/deadbeef/98765').and_return(response)
163
+ JIRA::Resource::Deadbeef.should_receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
164
+ end
165
+
166
+ it "sets expanded to true after fetch" do
167
+ subject.expanded?.should be_false
168
+ subject.fetch
169
+ subject.expanded?.should be_true
170
+ end
171
+
172
+ it "performs a fetch" do
173
+ subject.expanded?.should be_false
174
+ subject.fetch
175
+ subject.self.should == "http://deadbeef/"
176
+ subject.id.should == "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
+ client.should_not_receive(:get)
190
+ subject.fetch
191
+ end
192
+ end
193
+
194
+ context "with expand parameter 'changelog'" do
195
+ it "fetchs changelogs '" do
196
+ response = double()
197
+ response.stub(:body).and_return('{"self":"http://deadbeef/","id":"98765","changelog":{"histories":[]}}')
198
+ client.should_receive(:get).with('/jira/rest/api/2/deadbeef/98765?expand=changelog').and_return(response)
199
+
200
+ JIRA::Resource::Deadbeef.should_receive(:collection_path).and_return('/jira/rest/api/2/deadbeef')
201
+
202
+ subject.fetch(false, {expand:'changelog'})
203
+
204
+ subject.self.should == "http://deadbeef/"
205
+ subject.id.should == "98765"
206
+ subject.changelog['histories'].should == []
207
+ end
208
+ end
209
+ end
210
+
211
+ describe "save" do
212
+
213
+ let(:response) { double() }
214
+
215
+ subject { JIRA::Resource::Deadbeef.new(client) }
216
+
217
+ before(:each) do
218
+ subject.should_receive(:url).and_return('/foo/bar')
219
+ end
220
+
221
+ it "POSTs a new record" do
222
+ response.stub(:body => '{"id":"123"}')
223
+ subject.stub(:new_record? => true)
224
+ client.should_receive(:post).with('/foo/bar','{"foo":"bar"}').and_return(response)
225
+ subject.save("foo" => "bar").should be_true
226
+ subject.id.should == "123"
227
+ subject.expanded.should be_false
228
+ end
229
+
230
+ it "PUTs an existing record" do
231
+ response.stub(:body => nil)
232
+ subject.stub(:new_record? => false)
233
+ client.should_receive(:put).with('/foo/bar','{"foo":"bar"}').and_return(response)
234
+ subject.save("foo" => "bar").should be_true
235
+ subject.expanded.should be_false
236
+ end
237
+
238
+ it "merges attrs on save" do
239
+ response.stub(:body => nil)
240
+ client.should_receive(:post).with('/foo/bar','{"foo":{"fum":"dum"}}').and_return(response)
241
+ subject.attrs = {"foo" => {"bar" => "baz"}}
242
+ subject.save({"foo" => {"fum" => "dum"}})
243
+ subject.foo.should == {"bar" => "baz", "fum" => "dum"}
244
+ end
245
+
246
+ 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
247
+ response.stub(:body => '{"errorMessages":["blah"]}', :status => 400)
248
+ subject.stub(:new_record? => false)
249
+ client.should_receive(:put).with('/foo/bar','{"invalid_field":"foobar"}').and_raise(JIRA::HTTPError.new(response))
250
+ subject.save("invalid_field" => "foobar").should be_false
251
+ end
252
+
253
+ end
254
+
255
+ describe "save!" do
256
+ let(:response) { double() }
257
+
258
+ subject { JIRA::Resource::Deadbeef.new(client) }
259
+
260
+ before(:each) do
261
+ subject.should_receive(:url).and_return('/foo/bar')
262
+ end
263
+
264
+ it "POSTs a new record" do
265
+ response.stub(:body => '{"id":"123"}')
266
+ subject.stub(:new_record? => true)
267
+ client.should_receive(:post).with('/foo/bar','{"foo":"bar"}').and_return(response)
268
+ subject.save!("foo" => "bar").should be_true
269
+ subject.id.should == "123"
270
+ subject.expanded.should be_false
271
+ end
272
+
273
+ it "PUTs an existing record" do
274
+ response.stub(:body => nil)
275
+ subject.stub(:new_record? => false)
276
+ client.should_receive(:put).with('/foo/bar','{"foo":"bar"}').and_return(response)
277
+ subject.save!("foo" => "bar").should be_true
278
+ subject.expanded.should be_false
279
+ end
280
+
281
+ it "throws an exception when an invalid field is set" do
282
+ response.stub(:body => '{"errorMessages":["blah"]}', :status => 400)
283
+ subject.stub(:new_record? => false)
284
+ client.should_receive(:put).with('/foo/bar','{"invalid_field":"foobar"}').and_raise(JIRA::HTTPError.new(response))
285
+ lambda do
286
+ subject.save!("invalid_field" => "foobar")
287
+ end.should 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
+ subject.foo.should == {"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
+ subject.foo.should == {"bar" => "baz", "fum" => "dum"}
302
+ end
303
+ end
304
+
305
+ describe "delete" do
306
+
307
+ before(:each) do
308
+ client.should_receive(:delete).with('/foo/bar')
309
+ subject.stub(:url => '/foo/bar')
310
+ end
311
+
312
+ it "flags itself as deleted" do
313
+ subject.deleted?.should be_false
314
+ subject.delete
315
+ subject.deleted?.should be_true
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
+ subject.new_record?.should be_true
329
+ end
330
+
331
+ it "returns false for new_record? when id is set" do
332
+ subject.attrs['id'] = '123'
333
+ subject.new_record?.should be_false
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
+ subject.has_errors?.should be_true
343
+ end
344
+
345
+ it "returns false when the response does not contain any errors" do
346
+ subject.has_errors?.should be_false
347
+ end
348
+
349
+ end
350
+
351
+ describe 'url' do
352
+
353
+ before(:each) do
354
+ client.stub(: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
+ subject.url.should == "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
+ subject.url.should == "/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
+ subject.url.should == "/foo/bar/deadbeef"
373
+ end
374
+
375
+ it "has a class method for the collection path" do
376
+ JIRA::Resource::Deadbeef.collection_path(client).should == "/foo/bar/deadbeef"
377
+ #Should accept an optional prefix (flum in this case)
378
+ JIRA::Resource::Deadbeef.collection_path(client, '/flum/').should == "/foo/bar/flum/deadbeef"
379
+ end
380
+
381
+ it "has a class method for the singular path" do
382
+ JIRA::Resource::Deadbeef.singular_path(client, 'abc123').should == "/foo/bar/deadbeef/abc123"
383
+ #Should accept an optional prefix (flum in this case)
384
+ JIRA::Resource::Deadbeef.singular_path(client, 'abc123', '/flum/').should == "/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
+ subject.to_s.should match(/#<JIRA::Resource::Deadbeef:\d+ @attrs=#{Regexp.quote(attrs.inspect)}>/)
393
+ end
394
+
395
+ it "returns the key attribute" do
396
+ subject.class.key_attribute.should == :id
397
+ end
398
+
399
+ it "returns the key value" do
400
+ subject.attrs['id'] = '123'
401
+ subject.key_value.should == '123'
402
+ end
403
+
404
+ it "converts to json" do
405
+ subject.attrs = {"foo" => "bar","dead" => "beef"}
406
+
407
+ subject.to_json.should == 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 = double()
416
+ response.stub(:body).and_return('{"foo":"bar"}')
417
+
418
+ subject.set_attrs_from_response(response).should == {'foo' => 'bar'}
419
+ subject.foo.should == "bar"
420
+ end
421
+
422
+ it "doesn't clobber existing attrs not in response" do
423
+ response = double()
424
+ response.stub(:body).and_return('{"foo":"bar"}')
425
+
426
+ subject.attrs = {'flum' => 'flar'}
427
+ subject.set_attrs_from_response(response).should == {'foo' => 'bar'}
428
+ subject.foo.should == "bar"
429
+ subject.flum.should == "flar"
430
+ end
431
+
432
+ it "handles nil response body" do
433
+ response = double()
434
+ response.stub(:body).and_return(nil)
435
+
436
+ subject.attrs = {'flum' => 'flar'}
437
+ subject.set_attrs_from_response(response).should be_nil
438
+ subject.flum.should == 'flar'
439
+ end
440
+ end
441
+
442
+ describe "nesting" do
443
+
444
+ it "defaults collection_attributes_are_nested to false" do
445
+ JIRA::Resource::Deadbeef.collection_attributes_are_nested.should be_false
446
+ end
447
+
448
+ it "allows collection_attributes_are_nested to be set" do
449
+ JIRA::Resource::Deadbeef.nested_collections true
450
+ JIRA::Resource::Deadbeef.collection_attributes_are_nested.should be_true
451
+ end
452
+
453
+ end
454
+
455
+ describe "has_many" do
456
+
457
+ subject { JIRA::Resource::HasManyExample.new(client, :attrs => {'deadbeefs' => [{'id' => '123'}]}) }
458
+
459
+ it "returns a collection of instances for has_many relationships" do
460
+ subject.deadbeefs.class.should == JIRA::HasManyProxy
461
+ subject.deadbeefs.length.should == 1
462
+ subject.deadbeefs.each do |deadbeef|
463
+ deadbeef.class.should == JIRA::Resource::Deadbeef
464
+ end
465
+ end
466
+
467
+ it "returns an empty collection for empty has_many relationships" do
468
+ subject = JIRA::Resource::HasManyExample.new(client)
469
+ subject.deadbeefs.length.should == 0
470
+ end
471
+
472
+ it "allows the has_many attributes to be nested inside another attribute" do
473
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {'nested' => {'brunchmuffins' => [{'id' => '123'},{'id' => '456'}]}})
474
+ subject.brunchmuffins.length.should == 2
475
+ subject.brunchmuffins.each do |brunchmuffin|
476
+ brunchmuffin.class.should == JIRA::Resource::Deadbeef
477
+ end
478
+ end
479
+
480
+ it "allows it to be deeply nested" do
481
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {'nested' => {
482
+ 'breakfastscone' => { 'breakfastscones' => [{'id' => '123'},{'id' => '456'}] }
483
+ }})
484
+ subject.breakfastscones.length.should == 2
485
+ subject.breakfastscones.each do |breakfastscone|
486
+ breakfastscone.class.should == JIRA::Resource::Deadbeef
487
+ end
488
+ end
489
+
490
+ it "short circuits missing deeply nested attrs" do
491
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {
492
+ 'nested' => {}
493
+ })
494
+ subject.breakfastscones.length.should == 0
495
+ end
496
+
497
+ it "allows the attribute key to be specified" do
498
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {'irregularlyNamedThings' => [{'id' => '123'},{'id' => '456'}]})
499
+ subject.irregularly_named_things.length.should == 2
500
+ subject.irregularly_named_things.each do |thing|
501
+ thing.class.should == JIRA::Resource::Deadbeef
502
+ end
503
+ end
504
+
505
+ it "can build child instances" do
506
+ deadbeef = subject.deadbeefs.build
507
+ deadbeef.class.should == JIRA::Resource::Deadbeef
508
+ end
509
+
510
+ end
511
+
512
+ describe "has_one" do
513
+
514
+ subject { JIRA::Resource::HasOneExample.new(client, :attrs => {'deadbeef' => {'id' => '123'}}) }
515
+
516
+ it "returns an instance for a has one relationship" do
517
+ subject.deadbeef.class.should == JIRA::Resource::Deadbeef
518
+ subject.deadbeef.id.should == '123'
519
+ end
520
+
521
+ it "returns nil when resource attribute is nonexistent" do
522
+ subject = JIRA::Resource::HasOneExample.new(client)
523
+ subject.deadbeef.should be_nil
524
+ end
525
+
526
+ it "returns an instance with a different class name to the attribute name" do
527
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'muffin' => {'id' => '123'}})
528
+ subject.muffin.class.should == JIRA::Resource::Deadbeef
529
+ subject.muffin.id.should == '123'
530
+ end
531
+
532
+ it "allows the has_one attributes to be nested inside another attribute" do
533
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'nested' => {'brunchmuffin' => {'id' => '123'}}})
534
+ subject.brunchmuffin.class.should == JIRA::Resource::Deadbeef
535
+ subject.brunchmuffin.id.should == '123'
536
+ end
537
+
538
+ it "allows it to be deeply nested" do
539
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'nested' => {
540
+ 'breakfastscone' => { 'breakfastscone' => {'id' => '123'} }
541
+ }})
542
+ subject.breakfastscone.class.should == JIRA::Resource::Deadbeef
543
+ subject.breakfastscone.id.should == '123'
544
+ end
545
+
546
+ it "allows the attribute key to be specified" do
547
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'irregularlyNamedThing' => {'id' => '123'}})
548
+ subject.irregularly_named_thing.class.should == JIRA::Resource::Deadbeef
549
+ subject.irregularly_named_thing.id.should == '123'
550
+ end
551
+
552
+ end
553
+
554
+ describe "belongs_to" do
555
+
556
+ class JIRA::Resource::BelongsToExample < JIRA::Base
557
+ belongs_to :deadbeef
558
+ end
559
+
560
+ let(:deadbeef) { JIRA::Resource::Deadbeef.new(client, :attrs => {'id' => "999"}) }
561
+
562
+ subject { JIRA::Resource::BelongsToExample.new(client, :attrs => {'id' => '123'}, :deadbeef => deadbeef) }
563
+
564
+ it "sets up an accessor for the belongs to relationship" do
565
+ subject.deadbeef.should == deadbeef
566
+ end
567
+
568
+ it "raises an exception when initialized without a belongs_to instance" do
569
+ lambda do
570
+ JIRA::Resource::BelongsToExample.new(client, :attrs => {'id' => '123'})
571
+ end.should raise_exception(ArgumentError,"Required option :deadbeef missing")
572
+ end
573
+
574
+ it "returns the right url" do
575
+ client.stub(:options => { :rest_base_path => "/foo" })
576
+ subject.url.should == "/foo/deadbeef/999/belongstoexample/123"
577
+ end
578
+
579
+ it "can be initialized with an instance or a key value" do
580
+ client.stub(:options => { :rest_base_path => "/foo" })
581
+ subject = JIRA::Resource::BelongsToExample.new(client, :attrs => {'id' => '123'}, :deadbeef_id => '987')
582
+ subject.url.should == "/foo/deadbeef/987/belongstoexample/123"
583
+ end
584
+
585
+ end
586
+ end