jira-ruby 0.0.2 → 0.0.3

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