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.
- data/README.rdoc +259 -0
- data/Rakefile +9 -0
- data/example.rb +47 -4
- data/jira-ruby.gemspec +5 -3
- data/lib/jira.rb +21 -7
- data/lib/jira/base.rb +466 -0
- data/lib/jira/base_factory.rb +49 -0
- data/lib/jira/client.rb +79 -8
- data/lib/jira/has_many_proxy.rb +43 -0
- data/lib/jira/http_error.rb +16 -0
- data/lib/jira/resource/attachment.rb +12 -0
- data/lib/jira/resource/comment.rb +14 -0
- data/lib/jira/resource/component.rb +4 -9
- data/lib/jira/resource/issue.rb +49 -5
- data/lib/jira/resource/issuetype.rb +10 -0
- data/lib/jira/resource/priority.rb +10 -0
- data/lib/jira/resource/project.rb +24 -3
- data/lib/jira/resource/status.rb +10 -0
- data/lib/jira/resource/user.rb +14 -0
- data/lib/jira/resource/version.rb +10 -0
- data/lib/jira/resource/worklog.rb +16 -0
- data/lib/jira/version.rb +2 -2
- data/spec/integration/attachment_spec.rb +26 -0
- data/spec/integration/comment_spec.rb +55 -0
- data/spec/integration/component_spec.rb +25 -52
- data/spec/integration/issue_spec.rb +50 -47
- data/spec/integration/issuetype_spec.rb +27 -0
- data/spec/integration/priority_spec.rb +27 -0
- data/spec/integration/project_spec.rb +32 -24
- data/spec/integration/status_spec.rb +27 -0
- data/spec/integration/user_spec.rb +25 -0
- data/spec/integration/version_spec.rb +43 -0
- data/spec/integration/worklog_spec.rb +55 -0
- data/spec/jira/base_factory_spec.rb +46 -0
- data/spec/jira/base_spec.rb +555 -0
- data/spec/jira/client_spec.rb +12 -12
- data/spec/jira/has_many_proxy_spec.rb +45 -0
- data/spec/jira/{resource/http_error_spec.rb → http_error_spec.rb} +1 -1
- data/spec/jira/resource/attachment_spec.rb +20 -0
- data/spec/jira/resource/issue_spec.rb +83 -0
- data/spec/jira/resource/project_factory_spec.rb +3 -3
- data/spec/jira/resource/project_spec.rb +28 -0
- data/spec/jira/resource/worklog_spec.rb +24 -0
- data/spec/mock_responses/attachment/10000.json +20 -0
- data/spec/mock_responses/component/10000.invalid.put.json +5 -0
- data/spec/mock_responses/issue.json +1108 -0
- data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
- data/spec/mock_responses/issue/10002.json +13 -1
- data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
- data/spec/mock_responses/issue/10002/comment.json +65 -0
- data/spec/mock_responses/issue/10002/comment.post.json +29 -0
- data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
- data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
- data/spec/mock_responses/issue/10002/worklog.json +98 -0
- data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
- data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
- data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
- data/spec/mock_responses/issuetype.json +42 -0
- data/spec/mock_responses/issuetype/5.json +8 -0
- data/spec/mock_responses/priority.json +42 -0
- data/spec/mock_responses/priority/1.json +8 -0
- data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
- data/spec/mock_responses/project/SAMPLEPROJECT.json +15 -1
- data/spec/mock_responses/status.json +37 -0
- data/spec/mock_responses/status/1.json +7 -0
- data/spec/mock_responses/user?username=admin.json +17 -0
- data/spec/mock_responses/version.post.json +7 -0
- data/spec/mock_responses/version/10000.invalid.put.json +5 -0
- data/spec/mock_responses/version/10000.json +11 -0
- data/spec/mock_responses/version/10000.put.json +7 -0
- data/spec/spec_helper.rb +7 -12
- data/spec/support/matchers/have_attributes.rb +11 -0
- data/spec/support/matchers/have_many.rb +9 -0
- data/spec/support/matchers/have_one.rb +5 -0
- data/spec/support/shared_examples/integration.rb +174 -0
- metadata +139 -24
- data/README.markdown +0 -81
- data/lib/jira/resource/base.rb +0 -148
- data/lib/jira/resource/base_factory.rb +0 -44
- data/lib/jira/resource/http_error.rb +0 -17
- data/spec/jira/resource/base_factory_spec.rb +0 -36
- 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
|