jira-ruby 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/README.rdoc +259 -0
  2. data/Rakefile +9 -0
  3. data/example.rb +47 -4
  4. data/jira-ruby.gemspec +5 -3
  5. data/lib/jira.rb +21 -7
  6. data/lib/jira/base.rb +466 -0
  7. data/lib/jira/base_factory.rb +49 -0
  8. data/lib/jira/client.rb +79 -8
  9. data/lib/jira/has_many_proxy.rb +43 -0
  10. data/lib/jira/http_error.rb +16 -0
  11. data/lib/jira/resource/attachment.rb +12 -0
  12. data/lib/jira/resource/comment.rb +14 -0
  13. data/lib/jira/resource/component.rb +4 -9
  14. data/lib/jira/resource/issue.rb +49 -5
  15. data/lib/jira/resource/issuetype.rb +10 -0
  16. data/lib/jira/resource/priority.rb +10 -0
  17. data/lib/jira/resource/project.rb +24 -3
  18. data/lib/jira/resource/status.rb +10 -0
  19. data/lib/jira/resource/user.rb +14 -0
  20. data/lib/jira/resource/version.rb +10 -0
  21. data/lib/jira/resource/worklog.rb +16 -0
  22. data/lib/jira/version.rb +2 -2
  23. data/spec/integration/attachment_spec.rb +26 -0
  24. data/spec/integration/comment_spec.rb +55 -0
  25. data/spec/integration/component_spec.rb +25 -52
  26. data/spec/integration/issue_spec.rb +50 -47
  27. data/spec/integration/issuetype_spec.rb +27 -0
  28. data/spec/integration/priority_spec.rb +27 -0
  29. data/spec/integration/project_spec.rb +32 -24
  30. data/spec/integration/status_spec.rb +27 -0
  31. data/spec/integration/user_spec.rb +25 -0
  32. data/spec/integration/version_spec.rb +43 -0
  33. data/spec/integration/worklog_spec.rb +55 -0
  34. data/spec/jira/base_factory_spec.rb +46 -0
  35. data/spec/jira/base_spec.rb +555 -0
  36. data/spec/jira/client_spec.rb +12 -12
  37. data/spec/jira/has_many_proxy_spec.rb +45 -0
  38. data/spec/jira/{resource/http_error_spec.rb → http_error_spec.rb} +1 -1
  39. data/spec/jira/resource/attachment_spec.rb +20 -0
  40. data/spec/jira/resource/issue_spec.rb +83 -0
  41. data/spec/jira/resource/project_factory_spec.rb +3 -3
  42. data/spec/jira/resource/project_spec.rb +28 -0
  43. data/spec/jira/resource/worklog_spec.rb +24 -0
  44. data/spec/mock_responses/attachment/10000.json +20 -0
  45. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  46. data/spec/mock_responses/issue.json +1108 -0
  47. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  48. data/spec/mock_responses/issue/10002.json +13 -1
  49. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  50. data/spec/mock_responses/issue/10002/comment.json +65 -0
  51. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  52. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  53. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  54. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  55. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  56. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  57. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  58. data/spec/mock_responses/issuetype.json +42 -0
  59. data/spec/mock_responses/issuetype/5.json +8 -0
  60. data/spec/mock_responses/priority.json +42 -0
  61. data/spec/mock_responses/priority/1.json +8 -0
  62. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  63. data/spec/mock_responses/project/SAMPLEPROJECT.json +15 -1
  64. data/spec/mock_responses/status.json +37 -0
  65. data/spec/mock_responses/status/1.json +7 -0
  66. data/spec/mock_responses/user?username=admin.json +17 -0
  67. data/spec/mock_responses/version.post.json +7 -0
  68. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  69. data/spec/mock_responses/version/10000.json +11 -0
  70. data/spec/mock_responses/version/10000.put.json +7 -0
  71. data/spec/spec_helper.rb +7 -12
  72. data/spec/support/matchers/have_attributes.rb +11 -0
  73. data/spec/support/matchers/have_many.rb +9 -0
  74. data/spec/support/matchers/have_one.rb +5 -0
  75. data/spec/support/shared_examples/integration.rb +174 -0
  76. metadata +139 -24
  77. data/README.markdown +0 -81
  78. data/lib/jira/resource/base.rb +0 -148
  79. data/lib/jira/resource/base_factory.rb +0 -44
  80. data/lib/jira/resource/http_error.rb +0 -17
  81. data/spec/jira/resource/base_factory_spec.rb +0 -36
  82. data/spec/jira/resource/base_spec.rb +0 -292
@@ -0,0 +1,49 @@
1
+ module JIRA
2
+
3
+ # This is the base class for all the JIRA resource factory instances.
4
+ class BaseFactory
5
+
6
+ attr_reader :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ # Return the name of the class which this factory generates, i.e.
13
+ # JIRA::Resource::FooFactory creates JIRA::Resource::Foo instances.
14
+ def target_class
15
+ # Need to do a little bit of work here as Module.const_get doesn't work
16
+ # with nested class names, i.e. JIRA::Resource::Foo.
17
+ #
18
+ # So create a method chain from the class componenets. This code will
19
+ # unroll to:
20
+ # Module.const_get('JIRA').const_get('Resource').const_get('Foo')
21
+ #
22
+ target_class_name = self.class.name.sub(/Factory$/, '')
23
+ class_components = target_class_name.split('::')
24
+
25
+ class_components.inject(Module) do |mod, const_name|
26
+ mod.const_get(const_name)
27
+ end
28
+ end
29
+
30
+ def self.delegate_to_target_class(*method_names)
31
+ method_names.each do |method_name|
32
+ define_method method_name do |*args|
33
+ target_class.send(method_name, @client, *args)
34
+ end
35
+ end
36
+ end
37
+
38
+ # The priciple purpose of this class is to delegate methods to the corresponding
39
+ # non-factory class and automatically prepend the client argument to the argument
40
+ # list.
41
+ delegate_to_target_class :all, :find, :collection_path, :singular_path
42
+
43
+ # This method needs special handling as it has a default argument value
44
+ def build(attrs={})
45
+ target_class.build(@client, attrs)
46
+ end
47
+
48
+ end
49
+ end
data/lib/jira/client.rb CHANGED
@@ -2,19 +2,44 @@ require 'oauth'
2
2
  require 'json'
3
3
  require 'forwardable'
4
4
 
5
- module Jira
5
+ module JIRA
6
+
7
+ # This class is the main access point for all JIRA::Resource instances.
8
+ #
9
+ # The client must be initialized with a consumer_key and consumer secret,
10
+ # and an optional hash of extra configuration options. The available options
11
+ # are:
12
+ #
13
+ # :site => 'http://localhost:2990',
14
+ # :signature_method => 'RSA-SHA1',
15
+ # :request_token_path => "/jira/plugins/servlet/oauth/request-token",
16
+ # :authorize_path => "/jira/plugins/servlet/oauth/authorize",
17
+ # :access_token_path => "/jira/plugins/servlet/oauth/access-token",
18
+ # :private_key_file => "rsakey.pem",
19
+ # :rest_base_path => "/jira/rest/api/2"
20
+ #
21
+ #
22
+ # See the JIRA::Base class methods for all of the available methods on these accessor
23
+ # objects.
24
+ #
6
25
  class Client
7
26
 
8
27
  extend Forwardable
9
28
 
29
+ # This exception is thrown when the client is used before the OAuth access token
30
+ # has been initialized.
10
31
  class UninitializedAccessTokenError < StandardError
11
32
  def message
12
33
  "init_access_token must be called before using the client"
13
34
  end
14
35
  end
15
36
 
37
+ # The OAuth::Consumer instance used by this client
16
38
  attr_accessor :consumer
39
+
40
+ # The configuration options for this client instance
17
41
  attr_reader :options
42
+
18
43
  delegate [:key, :secret, :get_request_token] => :consumer
19
44
 
20
45
  DEFAULT_OPTIONS = {
@@ -35,34 +60,74 @@ module Jira
35
60
  @consumer = OAuth::Consumer.new(consumer_key,consumer_secret,options)
36
61
  end
37
62
 
38
- def Project
39
- Jira::Resource::ProjectFactory.new(self)
63
+ def Project # :nodoc:
64
+ JIRA::Resource::ProjectFactory.new(self)
65
+ end
66
+
67
+ def Issue # :nodoc:
68
+ JIRA::Resource::IssueFactory.new(self)
69
+ end
70
+
71
+ def Component # :nodoc:
72
+ JIRA::Resource::ComponentFactory.new(self)
73
+ end
74
+
75
+ def User # :nodoc:
76
+ JIRA::Resource::UserFactory.new(self)
77
+ end
78
+
79
+ def Issuetype # :nodoc:
80
+ JIRA::Resource::IssuetypeFactory.new(self)
81
+ end
82
+
83
+ def Priority # :nodoc:
84
+ JIRA::Resource::PriorityFactory.new(self)
85
+ end
86
+
87
+ def Status # :nodoc:
88
+ JIRA::Resource::StatusFactory.new(self)
89
+ end
90
+
91
+ def Comment # :nodoc:
92
+ JIRA::Resource::CommentFactory.new(self)
93
+ end
94
+
95
+ def Attachment # :nodoc:
96
+ JIRA::Resource::AttachmentFactory.new(self)
40
97
  end
41
98
 
42
- def Issue
43
- Jira::Resource::IssueFactory.new(self)
99
+ def Worklog # :nodoc:
100
+ JIRA::Resource::WorklogFactory.new(self)
44
101
  end
45
102
 
46
- def Component
47
- Jira::Resource::ComponentFactory.new(self)
103
+ def Version # :nodoc:
104
+ JIRA::Resource::VersionFactory.new(self)
48
105
  end
49
106
 
107
+ # Returns the current request token if it is set, else it creates
108
+ # and sets a new token.
50
109
  def request_token
51
110
  @request_token ||= get_request_token
52
111
  end
53
112
 
113
+ # Sets the request token from a given token and secret.
54
114
  def set_request_token(token, secret)
55
115
  @request_token = OAuth::RequestToken.new(@consumer, token, secret)
56
116
  end
57
117
 
118
+ # Initialises and returns a new access token from the params hash
119
+ # returned by the OAuth transaction.
58
120
  def init_access_token(params)
59
121
  @access_token = request_token.get_access_token(params)
60
122
  end
61
123
 
124
+ # Sets the access token from a preexisting token and secret.
62
125
  def set_access_token(token, secret)
63
126
  @access_token = OAuth::AccessToken.new(@consumer, token, secret)
64
127
  end
65
128
 
129
+ # Returns the current access token. Raises an
130
+ # JIRA::Client::UninitializedAccessTokenError exception if it is not set.
66
131
  def access_token
67
132
  raise UninitializedAccessTokenError.new unless @access_token
68
133
  @access_token
@@ -89,9 +154,15 @@ module Jira
89
154
  request(:put, path, body, merge_default_headers(headers))
90
155
  end
91
156
 
157
+ # Sends the specified HTTP request to the REST API through the
158
+ # OAuth token.
159
+ #
160
+ # Returns the response if the request was successful (HTTP::2xx) and
161
+ # raises a JIRA::HTTPError if it was not successful, with the response
162
+ # attached.
92
163
  def request(http_method, path, *arguments)
93
164
  response = access_token.request(http_method, path, *arguments)
94
- raise Resource::HTTPError.new(response) unless response.kind_of?(Net::HTTPSuccess)
165
+ raise HTTPError.new(response) unless response.kind_of?(Net::HTTPSuccess)
95
166
  response
96
167
  end
97
168
 
@@ -0,0 +1,43 @@
1
+ #
2
+ # Whenever a collection from a has_many relationship is accessed, an instance
3
+ # of this class is returned. This instance wraps the Array of instances in
4
+ # the collection with an extra build method, which allows new instances to be
5
+ # built on the collection with the correct properties.
6
+ #
7
+ # In practice, instances of this class behave exactly like an Array.
8
+ #
9
+ class JIRA::HasManyProxy
10
+
11
+ attr_reader :target_class, :parent
12
+ attr_accessor :collection
13
+
14
+ def initialize(parent, target_class, collection = [])
15
+ @parent = parent
16
+ @target_class = target_class
17
+ @collection = collection
18
+ end
19
+
20
+ # Builds an instance of this class with the correct parent.
21
+ # For example, issue.comments.build(attrs) will initialize a
22
+ # comment as follows:
23
+ #
24
+ # JIRA::Resource::Comment.new(issue.client,
25
+ # :attrs => attrs,
26
+ # :issue => issue)
27
+ def build(attrs = {})
28
+ resource = target_class.new(parent.client, :attrs => attrs, parent.to_sym => parent)
29
+ collection << resource
30
+ resource
31
+ end
32
+
33
+ # Forces an HTTP request to fetch all instances of the target class that
34
+ # are associated with the parent
35
+ def all
36
+ target_class.all(parent.client, parent.to_sym => parent)
37
+ end
38
+
39
+ # Delegate any missing methods to the collection that this proxy wraps
40
+ def method_missing(method_name, *args, &block)
41
+ collection.send(method_name, *args, &block)
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ require 'forwardable'
2
+ module JIRA
3
+
4
+ class HTTPError < StandardError
5
+ extend Forwardable
6
+
7
+ delegate [:message, :code] => :response
8
+ attr_reader :response
9
+
10
+ def initialize(response)
11
+ @response = response
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,12 @@
1
+ module JIRA
2
+ module Resource
3
+
4
+ class AttachmentFactory < JIRA::BaseFactory # :nodoc:
5
+ end
6
+
7
+ class Attachment < JIRA::Base
8
+ has_one :author, :class => JIRA::Resource::User
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module JIRA
2
+ module Resource
3
+
4
+ class CommentFactory < JIRA::BaseFactory # :nodoc:
5
+ end
6
+
7
+ class Comment < JIRA::Base
8
+ belongs_to :issue
9
+
10
+ nested_collections true
11
+ end
12
+
13
+ end
14
+ end
@@ -1,15 +1,10 @@
1
- module Jira
1
+ module JIRA
2
2
  module Resource
3
3
 
4
- class ComponentFactory < BaseFactory ; end
5
-
6
- class Component < Base
7
-
8
- def self.key_attribute
9
- :id
10
- end
11
-
4
+ class ComponentFactory < JIRA::BaseFactory # :nodoc:
12
5
  end
13
6
 
7
+ class Component < JIRA::Base ; end
8
+
14
9
  end
15
10
  end
@@ -1,12 +1,56 @@
1
- module Jira
1
+ module JIRA
2
2
  module Resource
3
3
 
4
- class IssueFactory < BaseFactory ; end
4
+ class IssueFactory < JIRA::BaseFactory # :nodoc:
5
+ end
6
+
7
+ class Issue < JIRA::Base
8
+
9
+ has_one :reporter, :class => JIRA::Resource::User,
10
+ :nested_under => 'fields'
11
+ has_one :assignee, :class => JIRA::Resource::User,
12
+ :nested_under => 'fields'
13
+ has_one :project, :nested_under => 'fields'
14
+
15
+ has_one :issuetype, :nested_under => 'fields'
16
+
17
+ has_one :priority, :nested_under => 'fields'
18
+
19
+ has_one :status, :nested_under => 'fields'
20
+
21
+ has_many :components, :nested_under => 'fields'
5
22
 
6
- class Issue < Base
23
+ has_many :comments, :nested_under => ['fields','comment']
24
+
25
+ has_many :attachments, :nested_under => 'fields',
26
+ :attribute_key => 'attachment'
27
+
28
+ has_many :versions, :nested_under => 'fields'
29
+
30
+ has_many :worklogs, :nested_under => ['fields','worklog']
31
+
32
+ def self.all(client)
33
+ response = client.get(client.options[:rest_base_path] + "/search")
34
+ json = parse_json(response.body)
35
+ json['issues'].map do |issue|
36
+ client.Issue.build(issue)
37
+ end
38
+ end
39
+
40
+ def respond_to?(method_name)
41
+ if attrs.keys.include?('fields') && attrs['fields'].keys.include?(method_name.to_s)
42
+ true
43
+ else
44
+ super(method_name)
45
+ end
46
+ end
7
47
 
8
- def self.key_attribute
9
- :id
48
+ def method_missing(method_name, *args, &block)
49
+ if attrs.keys.include?('fields') && attrs['fields'].keys.include?(method_name.to_s)
50
+ attrs['fields'][method_name.to_s]
51
+ else
52
+ super(method_name)
53
+ end
10
54
  end
11
55
 
12
56
  end
@@ -0,0 +1,10 @@
1
+ module JIRA
2
+ module Resource
3
+
4
+ class IssuetypeFactory < JIRA::BaseFactory # :nodoc:
5
+ end
6
+
7
+ class Issuetype < JIRA::Base ; end
8
+
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module JIRA
2
+ module Resource
3
+
4
+ class PriorityFactory < JIRA::BaseFactory # :nodoc:
5
+ end
6
+
7
+ class Priority < JIRA::Base ; end
8
+
9
+ end
10
+ end
@@ -1,9 +1,30 @@
1
- module Jira
1
+ module JIRA
2
2
  module Resource
3
3
 
4
- class ProjectFactory < BaseFactory ; end
4
+ class ProjectFactory < JIRA::BaseFactory # :nodoc:
5
+ end
5
6
 
6
- class Project < Base ; end
7
+ class Project < JIRA::Base
8
+
9
+ has_one :lead, :class => JIRA::Resource::User
10
+ has_many :components
11
+ has_many :issuetypes, :attribute_key => 'issueTypes'
12
+ has_many :versions
13
+
14
+ def self.key_attribute
15
+ :key
16
+ end
17
+
18
+ # Returns all the issues for this project
19
+ def issues
20
+ response = client.get(client.options[:rest_base_path] + "/search?jql=project%3D'#{key}'")
21
+ json = self.class.parse_json(response.body)
22
+ json['issues'].map do |issue|
23
+ client.Issue.build(issue)
24
+ end
25
+ end
26
+
27
+ end
7
28
 
8
29
  end
9
30
  end
@@ -0,0 +1,10 @@
1
+ module JIRA
2
+ module Resource
3
+
4
+ class StatusFactory < JIRA::BaseFactory # :nodoc:
5
+ end
6
+
7
+ class Status < JIRA::Base ; end
8
+
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module JIRA
2
+ module Resource
3
+
4
+ class UserFactory < JIRA::BaseFactory # :nodoc:
5
+ end
6
+
7
+ class User < JIRA::Base
8
+ def self.singular_path(client, key, prefix = '/')
9
+ collection_path(client, prefix) + '?username=' + key
10
+ end
11
+ end
12
+
13
+ end
14
+ end