jira-ruby-added-transitions 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/.gitignore +10 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +46 -0
  4. data/README.rdoc +327 -0
  5. data/Rakefile +28 -0
  6. data/example.rb +119 -0
  7. data/http-basic-example.rb +112 -0
  8. data/jira-ruby.gemspec +30 -0
  9. data/lib/jira.rb +31 -0
  10. data/lib/jira/base.rb +469 -0
  11. data/lib/jira/base_factory.rb +49 -0
  12. data/lib/jira/client.rb +151 -0
  13. data/lib/jira/has_many_proxy.rb +43 -0
  14. data/lib/jira/http_client.rb +41 -0
  15. data/lib/jira/http_error.rb +16 -0
  16. data/lib/jira/oauth_client.rb +84 -0
  17. data/lib/jira/railtie.rb +10 -0
  18. data/lib/jira/request_client.rb +18 -0
  19. data/lib/jira/resource/attachment.rb +12 -0
  20. data/lib/jira/resource/comment.rb +14 -0
  21. data/lib/jira/resource/component.rb +10 -0
  22. data/lib/jira/resource/issue.rb +72 -0
  23. data/lib/jira/resource/issuetype.rb +10 -0
  24. data/lib/jira/resource/priority.rb +10 -0
  25. data/lib/jira/resource/project.rb +30 -0
  26. data/lib/jira/resource/status.rb +10 -0
  27. data/lib/jira/resource/transition.rb +16 -0
  28. data/lib/jira/resource/user.rb +14 -0
  29. data/lib/jira/resource/version.rb +10 -0
  30. data/lib/jira/resource/worklog.rb +16 -0
  31. data/lib/jira/tasks.rb +0 -0
  32. data/lib/jira/version.rb +3 -0
  33. data/lib/tasks/generate.rake +18 -0
  34. data/spec/integration/attachment_spec.rb +23 -0
  35. data/spec/integration/comment_spec.rb +54 -0
  36. data/spec/integration/component_spec.rb +42 -0
  37. data/spec/integration/issue_spec.rb +94 -0
  38. data/spec/integration/issuetype_spec.rb +26 -0
  39. data/spec/integration/priority_spec.rb +27 -0
  40. data/spec/integration/project_spec.rb +56 -0
  41. data/spec/integration/status_spec.rb +27 -0
  42. data/spec/integration/user_spec.rb +25 -0
  43. data/spec/integration/version_spec.rb +43 -0
  44. data/spec/integration/worklog_spec.rb +55 -0
  45. data/spec/jira/base_factory_spec.rb +46 -0
  46. data/spec/jira/base_spec.rb +556 -0
  47. data/spec/jira/client_spec.rb +188 -0
  48. data/spec/jira/has_many_proxy_spec.rb +45 -0
  49. data/spec/jira/http_client_spec.rb +77 -0
  50. data/spec/jira/http_error_spec.rb +25 -0
  51. data/spec/jira/oauth_client_spec.rb +111 -0
  52. data/spec/jira/request_client_spec.rb +14 -0
  53. data/spec/jira/resource/attachment_spec.rb +20 -0
  54. data/spec/jira/resource/issue_spec.rb +83 -0
  55. data/spec/jira/resource/project_factory_spec.rb +13 -0
  56. data/spec/jira/resource/project_spec.rb +28 -0
  57. data/spec/jira/resource/worklog_spec.rb +24 -0
  58. data/spec/mock_responses/attachment/10000.json +20 -0
  59. data/spec/mock_responses/component.post.json +28 -0
  60. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  61. data/spec/mock_responses/component/10000.json +39 -0
  62. data/spec/mock_responses/component/10000.put.json +39 -0
  63. data/spec/mock_responses/issue.json +1108 -0
  64. data/spec/mock_responses/issue.post.json +5 -0
  65. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  66. data/spec/mock_responses/issue/10002.json +126 -0
  67. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  68. data/spec/mock_responses/issue/10002/comment.json +65 -0
  69. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  70. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  71. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  72. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  73. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  74. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  75. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  76. data/spec/mock_responses/issuetype.json +42 -0
  77. data/spec/mock_responses/issuetype/5.json +8 -0
  78. data/spec/mock_responses/priority.json +42 -0
  79. data/spec/mock_responses/priority/1.json +8 -0
  80. data/spec/mock_responses/project.json +12 -0
  81. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  82. data/spec/mock_responses/project/SAMPLEPROJECT.json +84 -0
  83. data/spec/mock_responses/status.json +37 -0
  84. data/spec/mock_responses/status/1.json +7 -0
  85. data/spec/mock_responses/user_username=admin.json +17 -0
  86. data/spec/mock_responses/version.post.json +7 -0
  87. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  88. data/spec/mock_responses/version/10000.json +11 -0
  89. data/spec/mock_responses/version/10000.put.json +7 -0
  90. data/spec/spec_helper.rb +22 -0
  91. data/spec/support/clients_helper.rb +16 -0
  92. data/spec/support/matchers/have_attributes.rb +11 -0
  93. data/spec/support/matchers/have_many.rb +9 -0
  94. data/spec/support/matchers/have_one.rb +5 -0
  95. data/spec/support/shared_examples/integration.rb +190 -0
  96. metadata +315 -0
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::Issuetype do
4
+
5
+ with_each_client do |site_url, client|
6
+ let(:client) { client }
7
+ let(:site_url) { site_url }
8
+
9
+ let(:key) { "5" }
10
+
11
+ let(:expected_attributes) do
12
+ {
13
+ 'self' => "http://localhost:2990/jira/rest/api/2/issuetype/5",
14
+ 'id' => key,
15
+ 'name' => 'Sub-task'
16
+ }
17
+ end
18
+
19
+ let(:expected_collection_length) { 5 }
20
+
21
+ it_should_behave_like "a resource"
22
+ it_should_behave_like "a resource with a collection GET endpoint"
23
+ it_should_behave_like "a resource with a singular GET endpoint"
24
+
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::Priority do
4
+
5
+ with_each_client do |site_url, client|
6
+ let(:client) { client }
7
+ let(:site_url) { site_url }
8
+
9
+
10
+ let(:key) { "1" }
11
+
12
+ let(:expected_attributes) do
13
+ {
14
+ 'self' => "http://localhost:2990/jira/rest/api/2/priority/1",
15
+ 'id' => key,
16
+ 'name' => 'Blocker'
17
+ }
18
+ end
19
+
20
+ let(:expected_collection_length) { 5 }
21
+
22
+ it_should_behave_like "a resource"
23
+ it_should_behave_like "a resource with a collection GET endpoint"
24
+ it_should_behave_like "a resource with a singular GET endpoint"
25
+
26
+ end
27
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::Project do
4
+
5
+ with_each_client do |site_url, client|
6
+ let(:client) { client }
7
+ let(:site_url) { site_url }
8
+
9
+
10
+ let(:key) { "SAMPLEPROJECT" }
11
+
12
+ let(:expected_attributes) do
13
+ {
14
+ 'self' => "http://localhost:2990/jira/rest/api/2/project/SAMPLEPROJECT",
15
+ 'key' => key,
16
+ 'name' => "Sample Project for Developing RoR RESTful API"
17
+ }
18
+ end
19
+
20
+ let(:expected_collection_length) { 1 }
21
+
22
+ it_should_behave_like "a resource"
23
+ it_should_behave_like "a resource with a collection GET endpoint"
24
+ it_should_behave_like "a resource with a singular GET endpoint"
25
+
26
+ describe "issues" do
27
+
28
+ it "returns all the issues" do
29
+ stub_request(:get, site_url + "/jira/rest/api/2/search?jql=project='SAMPLEPROJECT'").
30
+ to_return(:status => 200, :body => get_mock_response('project/SAMPLEPROJECT.issues.json'))
31
+ subject = client.Project.build('key' => key)
32
+ issues = subject.issues
33
+ issues.length.should == 11
34
+ issues.each do |issue|
35
+ issue.class.should == JIRA::Resource::Issue
36
+ issue.expanded?.should be_false
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ it "returns a collection of components" do
44
+
45
+ stub_request(:get, site_url + described_class.singular_path(client, key)).
46
+ to_return(:status => 200, :body => get_mock_response('project/SAMPLEPROJECT.json'))
47
+
48
+ subject = client.Project.find(key)
49
+ subject.components.length.should == 2
50
+ subject.components.each do |component|
51
+ component.class.should == JIRA::Resource::Component
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::Status do
4
+
5
+ with_each_client do |site_url, client|
6
+ let(:client) { client }
7
+ let(:site_url) { site_url }
8
+
9
+
10
+ let(:key) { "1" }
11
+
12
+ let(:expected_attributes) do
13
+ {
14
+ 'self' => "http://localhost:2990/jira/rest/api/2/status/1",
15
+ 'id' => key,
16
+ 'name' => 'Open'
17
+ }
18
+ end
19
+
20
+ let(:expected_collection_length) { 5 }
21
+
22
+ it_should_behave_like "a resource"
23
+ it_should_behave_like "a resource with a collection GET endpoint"
24
+ it_should_behave_like "a resource with a singular GET endpoint"
25
+
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::User do
4
+
5
+
6
+ with_each_client do |site_url, client|
7
+ let(:client) { client }
8
+ let(:site_url) { site_url }
9
+
10
+
11
+ let(:key) { "admin" }
12
+
13
+ let(:expected_attributes) do
14
+ {
15
+ 'self' => "http://localhost:2990/jira/rest/api/2/user?username=admin",
16
+ 'name' => key,
17
+ 'emailAddress' => 'admin@example.com'
18
+ }
19
+ end
20
+
21
+ it_should_behave_like "a resource"
22
+ it_should_behave_like "a resource with a singular GET endpoint"
23
+
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::Version do
4
+
5
+
6
+ with_each_client do |site_url, client|
7
+ let(:client) { client }
8
+ let(:site_url) { site_url }
9
+
10
+
11
+ let(:key) { "10000" }
12
+
13
+ let(:expected_attributes) do
14
+ {
15
+ 'self' => "http://localhost:2990/jira/rest/api/2/version/10000",
16
+ 'id' => key,
17
+ 'description' => "Initial version"
18
+ }
19
+ end
20
+
21
+ let(:attributes_for_post) {
22
+ {"name" => "2.0", "project" => "SAMPLEPROJECT" }
23
+ }
24
+ let(:expected_attributes_from_post) {
25
+ { "id" => "10001", "name" => "2.0" }
26
+ }
27
+
28
+ let(:attributes_for_put) {
29
+ {"name" => "2.0.0" }
30
+ }
31
+ let(:expected_attributes_from_put) {
32
+ { "id" => "10000", "name" => "2.0.0" }
33
+ }
34
+
35
+ it_should_behave_like "a resource"
36
+ it_should_behave_like "a resource with a singular GET endpoint"
37
+ it_should_behave_like "a resource with a DELETE endpoint"
38
+ it_should_behave_like "a resource with a POST endpoint"
39
+ it_should_behave_like "a resource with a PUT endpoint"
40
+ it_should_behave_like "a resource with a PUT endpoint that rejects invalid fields"
41
+
42
+ end
43
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe JIRA::Resource::Worklog do
4
+
5
+
6
+ with_each_client do |site_url, client|
7
+ let(:client) { client }
8
+ let(:site_url) { site_url }
9
+
10
+
11
+ let(:key) { "10000" }
12
+
13
+ let(:target) { JIRA::Resource::Worklog.new(client, :attrs => {'id' => '99999'}, :issue_id => '54321') }
14
+
15
+ let(:expected_collection_length) { 3 }
16
+
17
+ let(:belongs_to) {
18
+ JIRA::Resource::Issue.new(client, :attrs => {
19
+ 'id' => '10002', 'fields' => {
20
+ 'comment' => {'comments' => []}
21
+ }
22
+ })
23
+ }
24
+
25
+ let(:expected_attributes) do
26
+ {
27
+ 'self' => "http://localhost:2990/jira/rest/api/2/issue/10002/worklog/10000",
28
+ 'id' => key,
29
+ 'comment' => "Some epic work."
30
+ }
31
+ end
32
+
33
+ let(:attributes_for_post) {
34
+ {"timeSpent" => "2d"}
35
+ }
36
+ let(:expected_attributes_from_post) {
37
+ { "id" => "10001", "timeSpent" => "2d"}
38
+ }
39
+
40
+ let(:attributes_for_put) {
41
+ {"timeSpent" => "2d"}
42
+ }
43
+ let(:expected_attributes_from_put) {
44
+ { "id" => "10001", "timeSpent" => "4d"}
45
+ }
46
+
47
+ it_should_behave_like "a resource"
48
+ it_should_behave_like "a resource with a collection GET endpoint"
49
+ it_should_behave_like "a resource with a singular GET endpoint"
50
+ it_should_behave_like "a resource with a DELETE endpoint"
51
+ it_should_behave_like "a resource with a POST endpoint"
52
+ it_should_behave_like "a resource with a PUT endpoint"
53
+
54
+ end
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,556 @@
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
+ pending("Identified bug on real jira instance")
329
+ attrs['self'] = 'http://foo/bar'
330
+ subject.url.should == "http://foo/bar"
331
+ end
332
+
333
+ it "generates the URL from id if self not set" do
334
+ attrs['self'] = nil
335
+ attrs['id'] = '98765'
336
+ subject.url.should == "/foo/bar/deadbeef/98765"
337
+ end
338
+
339
+ it "generates the URL from collection_path if self and id not set" do
340
+ attrs['self'] = nil
341
+ attrs['id'] = nil
342
+ subject.url.should == "/foo/bar/deadbeef"
343
+ end
344
+
345
+ it "has a class method for the collection path" do
346
+ JIRA::Resource::Deadbeef.collection_path(client).should == "/foo/bar/deadbeef"
347
+ #Should accept an optional prefix (flum in this case)
348
+ JIRA::Resource::Deadbeef.collection_path(client, '/flum/').should == "/foo/bar/flum/deadbeef"
349
+ end
350
+
351
+ it "has a class method for the singular path" do
352
+ JIRA::Resource::Deadbeef.singular_path(client, 'abc123').should == "/foo/bar/deadbeef/abc123"
353
+ #Should accept an optional prefix (flum in this case)
354
+ JIRA::Resource::Deadbeef.singular_path(client, 'abc123', '/flum/').should == "/foo/bar/flum/deadbeef/abc123"
355
+ end
356
+ end
357
+
358
+ it "returns the formatted attrs from to_s" do
359
+ subject.attrs['foo'] = 'bar'
360
+ subject.attrs['dead'] = 'beef'
361
+
362
+ subject.to_s.should match(/#<JIRA::Resource::Deadbeef:\d+ @attrs=#{Regexp.quote(attrs.inspect)}>/)
363
+ end
364
+
365
+ it "returns the key attribute" do
366
+ subject.class.key_attribute.should == :id
367
+ end
368
+
369
+ it "returns the key value" do
370
+ subject.attrs['id'] = '123'
371
+ subject.key_value.should == '123'
372
+ end
373
+
374
+ it "converts to json" do
375
+ subject.attrs = {"foo" => "bar","dead" => "beef"}
376
+
377
+ subject.to_json.should == subject.attrs.to_json
378
+ end
379
+
380
+ describe "extract attrs from response" do
381
+
382
+ subject { JIRA::Resource::Deadbeef.new(client, :attrs => {}) }
383
+
384
+ it "sets the attrs from a response" do
385
+ response = mock()
386
+ response.stub(:body).and_return('{"foo":"bar"}')
387
+
388
+ subject.set_attrs_from_response(response).should == {'foo' => 'bar'}
389
+ subject.foo.should == "bar"
390
+ end
391
+
392
+ it "doesn't clobber existing attrs not in response" do
393
+ response = mock()
394
+ response.stub(:body).and_return('{"foo":"bar"}')
395
+
396
+ subject.attrs = {'flum' => 'flar'}
397
+ subject.set_attrs_from_response(response).should == {'foo' => 'bar'}
398
+ subject.foo.should == "bar"
399
+ subject.flum.should == "flar"
400
+ end
401
+
402
+ it "handles nil response body" do
403
+ response = mock()
404
+ response.stub(:body).and_return(nil)
405
+
406
+ subject.attrs = {'flum' => 'flar'}
407
+ subject.set_attrs_from_response(response).should be_nil
408
+ subject.flum.should == 'flar'
409
+ end
410
+ end
411
+
412
+ describe "nesting" do
413
+
414
+ it "defaults collection_attributes_are_nested to false" do
415
+ JIRA::Resource::Deadbeef.collection_attributes_are_nested.should be_false
416
+ end
417
+
418
+ it "allows collection_attributes_are_nested to be set" do
419
+ JIRA::Resource::Deadbeef.nested_collections true
420
+ JIRA::Resource::Deadbeef.collection_attributes_are_nested.should be_true
421
+ end
422
+
423
+ end
424
+
425
+ describe "has_many" do
426
+
427
+ subject { JIRA::Resource::HasManyExample.new(client, :attrs => {'deadbeefs' => [{'id' => '123'}]}) }
428
+
429
+ it "returns a collection of instances for has_many relationships" do
430
+ subject.deadbeefs.class.should == JIRA::HasManyProxy
431
+ subject.deadbeefs.length.should == 1
432
+ subject.deadbeefs.each do |deadbeef|
433
+ deadbeef.class.should == JIRA::Resource::Deadbeef
434
+ end
435
+ end
436
+
437
+ it "returns an empty collection for empty has_many relationships" do
438
+ subject = JIRA::Resource::HasManyExample.new(client)
439
+ subject.deadbeefs.length.should == 0
440
+ end
441
+
442
+ it "allows the has_many attributes to be nested inside another attribute" do
443
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {'nested' => {'brunchmuffins' => [{'id' => '123'},{'id' => '456'}]}})
444
+ subject.brunchmuffins.length.should == 2
445
+ subject.brunchmuffins.each do |brunchmuffin|
446
+ brunchmuffin.class.should == JIRA::Resource::Deadbeef
447
+ end
448
+ end
449
+
450
+ it "allows it to be deeply nested" do
451
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {'nested' => {
452
+ 'breakfastscone' => { 'breakfastscones' => [{'id' => '123'},{'id' => '456'}] }
453
+ }})
454
+ subject.breakfastscones.length.should == 2
455
+ subject.breakfastscones.each do |breakfastscone|
456
+ breakfastscone.class.should == JIRA::Resource::Deadbeef
457
+ end
458
+ end
459
+
460
+ it "short circuits missing deeply nested attrs" do
461
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {
462
+ 'nested' => {}
463
+ })
464
+ subject.breakfastscones.length.should == 0
465
+ end
466
+
467
+ it "allows the attribute key to be specified" do
468
+ subject = JIRA::Resource::HasManyExample.new(client, :attrs => {'irregularlyNamedThings' => [{'id' => '123'},{'id' => '456'}]})
469
+ subject.irregularly_named_things.length.should == 2
470
+ subject.irregularly_named_things.each do |thing|
471
+ thing.class.should == JIRA::Resource::Deadbeef
472
+ end
473
+ end
474
+
475
+ it "can build child instances" do
476
+ deadbeef = subject.deadbeefs.build
477
+ deadbeef.class.should == JIRA::Resource::Deadbeef
478
+ end
479
+
480
+ end
481
+
482
+ describe "has_one" do
483
+
484
+ subject { JIRA::Resource::HasOneExample.new(client, :attrs => {'deadbeef' => {'id' => '123'}}) }
485
+
486
+ it "returns an instance for a has one relationship" do
487
+ subject.deadbeef.class.should == JIRA::Resource::Deadbeef
488
+ subject.deadbeef.id.should == '123'
489
+ end
490
+
491
+ it "returns nil when resource attribute is nonexistent" do
492
+ subject = JIRA::Resource::HasOneExample.new(client)
493
+ subject.deadbeef.should be_nil
494
+ end
495
+
496
+ it "returns an instance with a different class name to the attribute name" do
497
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'muffin' => {'id' => '123'}})
498
+ subject.muffin.class.should == JIRA::Resource::Deadbeef
499
+ subject.muffin.id.should == '123'
500
+ end
501
+
502
+ it "allows the has_one attributes to be nested inside another attribute" do
503
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'nested' => {'brunchmuffin' => {'id' => '123'}}})
504
+ subject.brunchmuffin.class.should == JIRA::Resource::Deadbeef
505
+ subject.brunchmuffin.id.should == '123'
506
+ end
507
+
508
+ it "allows it to be deeply nested" do
509
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'nested' => {
510
+ 'breakfastscone' => { 'breakfastscone' => {'id' => '123'} }
511
+ }})
512
+ subject.breakfastscone.class.should == JIRA::Resource::Deadbeef
513
+ subject.breakfastscone.id.should == '123'
514
+ end
515
+
516
+ it "allows the attribute key to be specified" do
517
+ subject = JIRA::Resource::HasOneExample.new(client, :attrs => {'irregularlyNamedThing' => {'id' => '123'}})
518
+ subject.irregularly_named_thing.class.should == JIRA::Resource::Deadbeef
519
+ subject.irregularly_named_thing.id.should == '123'
520
+ end
521
+
522
+ end
523
+
524
+ describe "belongs_to" do
525
+
526
+ class JIRA::Resource::BelongsToExample < JIRA::Base
527
+ belongs_to :deadbeef
528
+ end
529
+
530
+ let(:deadbeef) { JIRA::Resource::Deadbeef.new(client, :attrs => {'id' => "999"}) }
531
+
532
+ subject { JIRA::Resource::BelongsToExample.new(client, :attrs => {'id' => '123'}, :deadbeef => deadbeef) }
533
+
534
+ it "sets up an accessor for the belongs to relationship" do
535
+ subject.deadbeef.should == deadbeef
536
+ end
537
+
538
+ it "raises an exception when initialized without a belongs_to instance" do
539
+ lambda do
540
+ JIRA::Resource::BelongsToExample.new(client, :attrs => {'id' => '123'})
541
+ end.should raise_exception(ArgumentError,"Required option :deadbeef missing")
542
+ end
543
+
544
+ it "returns the right url" do
545
+ client.stub(:options => { :rest_base_path => "/foo" })
546
+ subject.url.should == "/foo/deadbeef/999/belongstoexample/123"
547
+ end
548
+
549
+ it "can be initialized with an instance or a key value" do
550
+ client.stub(:options => { :rest_base_path => "/foo" })
551
+ subject = JIRA::Resource::BelongsToExample.new(client, :attrs => {'id' => '123'}, :deadbeef_id => '987')
552
+ subject.url.should == "/foo/deadbeef/987/belongstoexample/123"
553
+ end
554
+
555
+ end
556
+ end