jira-ruby-dmg 0.1.10

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/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