jira-ruby 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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