jira-ruby 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/Gemfile +5 -0
- data/README.markdown +81 -0
- data/Rakefile +9 -0
- data/example.rb +66 -0
- data/jira-ruby.gemspec +28 -0
- data/lib/jira.rb +12 -0
- data/lib/jira/client.rb +105 -0
- data/lib/jira/resource/base.rb +148 -0
- data/lib/jira/resource/base_factory.rb +44 -0
- data/lib/jira/resource/component.rb +15 -0
- data/lib/jira/resource/http_error.rb +17 -0
- data/lib/jira/resource/issue.rb +15 -0
- data/lib/jira/resource/project.rb +9 -0
- data/lib/jira/tasks.rb +0 -0
- data/lib/jira/version.rb +3 -0
- data/lib/tasks/generate.rake +16 -0
- data/spec/integration/component_spec.rb +70 -0
- data/spec/integration/issue_spec.rb +72 -0
- data/spec/integration/project_spec.rb +48 -0
- data/spec/jira/client_spec.rb +157 -0
- data/spec/jira/resource/base_factory_spec.rb +36 -0
- data/spec/jira/resource/base_spec.rb +292 -0
- data/spec/jira/resource/http_error_spec.rb +25 -0
- data/spec/jira/resource/project_factory_spec.rb +13 -0
- data/spec/mock_responses/component.post.json +28 -0
- data/spec/mock_responses/component/10000.json +39 -0
- data/spec/mock_responses/component/10000.put.json +39 -0
- data/spec/mock_responses/issue.post.json +5 -0
- data/spec/mock_responses/issue/10002.json +114 -0
- data/spec/mock_responses/project.json +12 -0
- data/spec/mock_responses/project/SAMPLEPROJECT.json +70 -0
- data/spec/spec_helper.rb +26 -0
- metadata +148 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
module Jira
|
2
|
+
module Resource
|
3
|
+
|
4
|
+
# This is the base class for all the Jira resource factory instances.
|
5
|
+
class BaseFactory
|
6
|
+
|
7
|
+
attr_reader :client
|
8
|
+
|
9
|
+
def initialize(client)
|
10
|
+
@client = client
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return the name of the class which this factory generates, i.e.
|
14
|
+
# Jira::Resource::FooFactory creates Jira::Resource::Foo instances.
|
15
|
+
def target_class
|
16
|
+
# Need to do a little bit of work here as Module.const_get doesn't work
|
17
|
+
# with nested class names, i.e. Jira::Resource::Foo.
|
18
|
+
#
|
19
|
+
# So create a method chain from the class componenets. This code will
|
20
|
+
# unroll to:
|
21
|
+
# Module.const_get('Jira').const_get('Resource').const_get('Foo')
|
22
|
+
#
|
23
|
+
target_class_name = self.class.name.sub(/Factory$/, '')
|
24
|
+
class_components = target_class_name.split('::')
|
25
|
+
|
26
|
+
class_components.inject(Module) do |mod, const_name|
|
27
|
+
mod.const_get(const_name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def all
|
32
|
+
target_class.all(@client)
|
33
|
+
end
|
34
|
+
|
35
|
+
def find(key)
|
36
|
+
target_class.find(@client, key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def build(attrs={})
|
40
|
+
target_class.build(@client, attrs)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
module Jira
|
3
|
+
module Resource
|
4
|
+
|
5
|
+
class HTTPError < StandardError
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
delegate [:message, :code] => :response
|
9
|
+
attr_reader :response
|
10
|
+
|
11
|
+
def initialize(response)
|
12
|
+
@response = response
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/jira/tasks.rb
ADDED
File without changes
|
data/lib/jira/version.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
namespace :jira do
|
2
|
+
desc "Generate a consumer key for your application"
|
3
|
+
task :generate_consumer_key do
|
4
|
+
#FIXME SERIOUSLY. THIS IS NOT A REAL SOLUTION. I FEEL SO UNCLEAN.
|
5
|
+
system("for i in {1..10}; do echo $RANDOM$RANDOM$RANDOM; done | md5 | awk '{ print \"You can use this as your consumer key: \" $0 }'")
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Run the system call to generate a RSA public certificate"
|
9
|
+
task :generate_public_cert do
|
10
|
+
puts "Executing 'openssl req -x509 -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem'"
|
11
|
+
system("openssl req -x509 -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem")
|
12
|
+
puts "Done. The RSA-SHA1 private keyfile is in the current directory: \'rsakey.pem\'."
|
13
|
+
puts "You will need to copy the following certificate into your application link configuration in Jira:"
|
14
|
+
system("cat rsacert.pem")
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Jira::Resource::Component 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(:expected_attributes) do
|
13
|
+
{
|
14
|
+
'self' => "http://localhost:2990/jira/rest/api/2/component/10000",
|
15
|
+
'id' => "10000",
|
16
|
+
'name' => "Cheesecake"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
before(:each) do
|
21
|
+
stub_request(:get,
|
22
|
+
"http://localhost:2990/jira/rest/api/2/component/10000").
|
23
|
+
to_return(:body => get_mock_response('component/10000.json'))
|
24
|
+
stub_request(:delete,
|
25
|
+
"http://localhost:2990/jira/rest/api/2/component/10000").
|
26
|
+
to_return(:body => nil)
|
27
|
+
stub_request(:post,
|
28
|
+
"http://localhost:2990/jira/rest/api/2/component").
|
29
|
+
with(:body => '{"name":"Test component","project":"SAMPLEPROJECT"}').
|
30
|
+
to_return(:status => 201, :body => get_mock_response('component.post.json'))
|
31
|
+
stub_request(:put,
|
32
|
+
"http://localhost:2990/jira/rest/api/2/component/10000").
|
33
|
+
with(:body => '{"name":"Jammy"}').
|
34
|
+
to_return(:status => 200, :body => get_mock_response('component/10000.put.json'))
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should get a single component by id" do
|
38
|
+
component = client.Component.find(10000)
|
39
|
+
|
40
|
+
component.should have_attributes(expected_attributes)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "builds and fetches single component" do
|
44
|
+
component = client.Component.build('id' => 10000)
|
45
|
+
component.fetch
|
46
|
+
|
47
|
+
component.should have_attributes(expected_attributes)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "deletes a component" do
|
51
|
+
component = client.Component.build('id' => "10000")
|
52
|
+
component.delete.should be_true
|
53
|
+
end
|
54
|
+
|
55
|
+
it "saves a new component" do
|
56
|
+
component = client.Component.build
|
57
|
+
component.save({"name" => "Test component", "project" => "SAMPLEPROJECT"}).should be_true
|
58
|
+
component.id.should == "10001"
|
59
|
+
component.name.should == "Test component"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "saves an existing component" do
|
63
|
+
component = client.Component.build('id' => '10000')
|
64
|
+
component.fetch
|
65
|
+
component.save('name' => 'Jammy').should be_true
|
66
|
+
component.id.should == "10000"
|
67
|
+
component.name.should == "Jammy"
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Jira::Resource::Issue 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(:expected_attributes) do
|
12
|
+
{
|
13
|
+
'self' => "http://localhost:2990/jira/rest/api/2/issue/10002",
|
14
|
+
'key' => "SAMPLEPROJECT-1",
|
15
|
+
'expand' => "renderedFields,names,schema,transitions,editmeta,changelog"
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
stub_request(:get,
|
21
|
+
"http://localhost:2990/jira/rest/api/2/issue/10002").
|
22
|
+
to_return(:body => get_mock_response('issue/10002.json'))
|
23
|
+
stub_request(:delete,
|
24
|
+
"http://localhost:2990/jira/rest/api/2/issue/10002").
|
25
|
+
to_return(:body => nil)
|
26
|
+
stub_request(:post, "http://localhost:2990/jira/rest/api/2/issue").
|
27
|
+
with(:body => '{"foo":"bar"}').
|
28
|
+
to_return(:body => get_mock_response('issue.post.json'))
|
29
|
+
stub_request(:put, "http://localhost:2990/jira/rest/api/2/issue/10002").
|
30
|
+
with(:body => '{"foo":"bar"}').
|
31
|
+
to_return(:body => nil)
|
32
|
+
stub_request(:get,
|
33
|
+
"http://localhost:2990/jira/rest/api/2/issue/99999").
|
34
|
+
to_return(:status => 404, :body => '{"errorMessages":["Issue Does Not Exist"],"errors": {}}')
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should get a single issue by key" do
|
38
|
+
issue = client.Issue.find('10002')
|
39
|
+
|
40
|
+
issue.should have_attributes(expected_attributes)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should handle issue not found" do
|
44
|
+
lambda do
|
45
|
+
issue = client.Issue.find('99999')
|
46
|
+
end.should raise_exception(Jira::Resource::HTTPError)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "builds and fetches single issue" do
|
50
|
+
issue = client.Issue.build('id' => '10002')
|
51
|
+
issue.fetch
|
52
|
+
|
53
|
+
issue.should have_attributes(expected_attributes)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "deletes an issue" do
|
57
|
+
issue = client.Issue.build('id' => "10002")
|
58
|
+
issue.delete.should be_true
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should save a new record" do
|
62
|
+
subject = described_class.new(client)
|
63
|
+
subject.save('foo' => 'bar').should be_true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should save an existing record" do
|
67
|
+
subject = client.Issue.build('id' => '10002')
|
68
|
+
subject.fetch
|
69
|
+
subject.save('foo' => 'bar').should be_true
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Jira::Resource::Project 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(:expected_attributes) do
|
12
|
+
{
|
13
|
+
'self' => "http://localhost:2990/jira/rest/api/2/project/SAMPLEPROJECT",
|
14
|
+
'key' => "SAMPLEPROJECT",
|
15
|
+
'name' => "Sample Project for Developing RoR RESTful API"
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
stub_request(:get,
|
21
|
+
"http://localhost:2990/jira/rest/api/2/project").
|
22
|
+
to_return(:body => get_mock_response('project.json'))
|
23
|
+
stub_request(:get,
|
24
|
+
"http://localhost:2990/jira/rest/api/2/project/SAMPLEPROJECT").
|
25
|
+
to_return(:body => get_mock_response('project/SAMPLEPROJECT.json'))
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should get all the projects" do
|
29
|
+
projects = client.Project.all
|
30
|
+
projects.length.should == 1
|
31
|
+
|
32
|
+
first = projects.first
|
33
|
+
first.should have_attributes(expected_attributes)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should get a single project by key" do
|
37
|
+
project = client.Project.find('SAMPLEPROJECT')
|
38
|
+
|
39
|
+
project.should have_attributes(expected_attributes)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "builds and fetches single project" do
|
43
|
+
project = client.Project.build('key' => 'SAMPLEPROJECT')
|
44
|
+
project.fetch
|
45
|
+
|
46
|
+
project.should have_attributes(expected_attributes)
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Jira::Client do
|
4
|
+
|
5
|
+
subject {Jira::Client.new('foo','bar')}
|
6
|
+
|
7
|
+
let(:response) do
|
8
|
+
response = mock("response")
|
9
|
+
response.stub(:kind_of?).with(Net::HTTPSuccess).and_return(true)
|
10
|
+
response
|
11
|
+
end
|
12
|
+
|
13
|
+
it "creates an instance" do
|
14
|
+
subject.class.should == Jira::Client
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets consumer key" do
|
18
|
+
subject.key.should == 'foo'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "sets consumer secret" do
|
22
|
+
subject.secret.should == 'bar'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "sets the default options" do
|
26
|
+
Jira::Client::DEFAULT_OPTIONS.each do |key, value|
|
27
|
+
subject.options[key].should == value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "allows the overriding of some options" do
|
32
|
+
# Check it overrides a given option ...
|
33
|
+
client = Jira::Client.new('foo', 'bar', :site => 'http://foo.com/')
|
34
|
+
client.options[:site].should == 'http://foo.com/'
|
35
|
+
|
36
|
+
# ... but leaves the rest intact
|
37
|
+
Jira::Client::DEFAULT_OPTIONS.keys.reject do |key|
|
38
|
+
key == :site
|
39
|
+
end.each do |key|
|
40
|
+
client.options[key].should == Jira::Client::DEFAULT_OPTIONS[key]
|
41
|
+
end
|
42
|
+
|
43
|
+
Jira::Client::DEFAULT_OPTIONS[:site].should_not == 'http://foo.com/'
|
44
|
+
end
|
45
|
+
|
46
|
+
# To avoid having to validate options after initialisation, e.g. setting
|
47
|
+
# client.options[:invalid] = 'foo'
|
48
|
+
it "freezes the options" do
|
49
|
+
subject.options.should be_frozen
|
50
|
+
end
|
51
|
+
|
52
|
+
it "creates a Oauth::Consumer on initialize" do
|
53
|
+
subject.consumer.class.should == OAuth::Consumer
|
54
|
+
subject.consumer.key.should == subject.key
|
55
|
+
subject.consumer.secret.should == subject.secret
|
56
|
+
end
|
57
|
+
|
58
|
+
it "returns an OAuth request_token" do
|
59
|
+
# Cannot just check for method delegation as http connection will be attempted
|
60
|
+
request_token = OAuth::RequestToken.new(subject.consumer)
|
61
|
+
subject.consumer.stub(:get_request_token => request_token)
|
62
|
+
subject.get_request_token.should == request_token
|
63
|
+
end
|
64
|
+
|
65
|
+
it "is possible to set the request token" do
|
66
|
+
token = mock()
|
67
|
+
OAuth::RequestToken.should_receive(:new).with(subject.consumer, 'foo', 'bar').and_return(token)
|
68
|
+
|
69
|
+
request_token = subject.set_request_token('foo', 'bar')
|
70
|
+
|
71
|
+
request_token.should == token
|
72
|
+
subject.request_token.should == token
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "access token" do
|
76
|
+
|
77
|
+
it "initializes the access token" do
|
78
|
+
request_token = OAuth::RequestToken.new(subject.consumer)
|
79
|
+
subject.consumer.stub(:get_request_token => request_token)
|
80
|
+
mock_access_token = mock()
|
81
|
+
request_token.should_receive(:get_access_token).with(:oauth_verifier => 'abc123').and_return(mock_access_token)
|
82
|
+
subject.init_access_token(:oauth_verifier => 'abc123')
|
83
|
+
subject.access_token.should == mock_access_token
|
84
|
+
end
|
85
|
+
|
86
|
+
it "raises an exception when accessing without initialisation" do
|
87
|
+
lambda do
|
88
|
+
subject.access_token
|
89
|
+
end.should raise_exception(Jira::Client::UninitializedAccessTokenError, "init_access_token must be called before using the client")
|
90
|
+
end
|
91
|
+
|
92
|
+
it "is possible to set the access token" do
|
93
|
+
token = mock()
|
94
|
+
OAuth::AccessToken.should_receive(:new).with(subject.consumer, 'foo', 'bar').and_return(token)
|
95
|
+
|
96
|
+
access_token = subject.set_access_token('foo', 'bar')
|
97
|
+
|
98
|
+
access_token.should == token
|
99
|
+
subject.access_token.should == token
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
describe "http" do
|
105
|
+
|
106
|
+
it "responds to the http methods" do
|
107
|
+
mock_access_token = mock()
|
108
|
+
subject.stub(:access_token => mock_access_token)
|
109
|
+
[:delete, :get, :head].each do |method|
|
110
|
+
mock_access_token.should_receive(:request).with(method, '/path', {'Accept' => 'application/json'}).and_return(response)
|
111
|
+
subject.send(method, '/path')
|
112
|
+
end
|
113
|
+
[:post, :put].each do |method|
|
114
|
+
mock_access_token.should_receive(:request).with(method,
|
115
|
+
'/path', '',
|
116
|
+
{'Accept' => 'application/json', 'Content-Type' => 'application/json'}).and_return(response)
|
117
|
+
subject.send(method, '/path')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it "performs a request" do
|
122
|
+
access_token = mock()
|
123
|
+
access_token.should_receive(:request).with(:get, '/foo').and_return(response)
|
124
|
+
subject.stub(:access_token => access_token)
|
125
|
+
subject.request(:get, '/foo')
|
126
|
+
end
|
127
|
+
|
128
|
+
it "raises an exception for non success responses" do
|
129
|
+
response = mock()
|
130
|
+
response.stub(:kind_of?).with(Net::HTTPSuccess).and_return(false)
|
131
|
+
access_token = mock()
|
132
|
+
access_token.should_receive(:request).with(:get, '/foo').and_return(response)
|
133
|
+
subject.stub(:access_token => access_token)
|
134
|
+
|
135
|
+
lambda do
|
136
|
+
subject.request(:get, '/foo')
|
137
|
+
end.should raise_exception(Jira::Resource::HTTPError)
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "Resource Factories" do
|
143
|
+
|
144
|
+
it "gets all projects" do
|
145
|
+
Jira::Resource::Project.should_receive(:all).with(subject).and_return([])
|
146
|
+
subject.Project.all.should == []
|
147
|
+
end
|
148
|
+
|
149
|
+
it "finds a single project" do
|
150
|
+
find_result = mock()
|
151
|
+
Jira::Resource::Project.should_receive(:find).with(subject, '123').and_return(find_result)
|
152
|
+
subject.Project.find('123').should == find_result
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|