jira-ruby 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. data/README.rdoc +259 -0
  2. data/Rakefile +9 -0
  3. data/example.rb +47 -4
  4. data/jira-ruby.gemspec +5 -3
  5. data/lib/jira.rb +21 -7
  6. data/lib/jira/base.rb +466 -0
  7. data/lib/jira/base_factory.rb +49 -0
  8. data/lib/jira/client.rb +79 -8
  9. data/lib/jira/has_many_proxy.rb +43 -0
  10. data/lib/jira/http_error.rb +16 -0
  11. data/lib/jira/resource/attachment.rb +12 -0
  12. data/lib/jira/resource/comment.rb +14 -0
  13. data/lib/jira/resource/component.rb +4 -9
  14. data/lib/jira/resource/issue.rb +49 -5
  15. data/lib/jira/resource/issuetype.rb +10 -0
  16. data/lib/jira/resource/priority.rb +10 -0
  17. data/lib/jira/resource/project.rb +24 -3
  18. data/lib/jira/resource/status.rb +10 -0
  19. data/lib/jira/resource/user.rb +14 -0
  20. data/lib/jira/resource/version.rb +10 -0
  21. data/lib/jira/resource/worklog.rb +16 -0
  22. data/lib/jira/version.rb +2 -2
  23. data/spec/integration/attachment_spec.rb +26 -0
  24. data/spec/integration/comment_spec.rb +55 -0
  25. data/spec/integration/component_spec.rb +25 -52
  26. data/spec/integration/issue_spec.rb +50 -47
  27. data/spec/integration/issuetype_spec.rb +27 -0
  28. data/spec/integration/priority_spec.rb +27 -0
  29. data/spec/integration/project_spec.rb +32 -24
  30. data/spec/integration/status_spec.rb +27 -0
  31. data/spec/integration/user_spec.rb +25 -0
  32. data/spec/integration/version_spec.rb +43 -0
  33. data/spec/integration/worklog_spec.rb +55 -0
  34. data/spec/jira/base_factory_spec.rb +46 -0
  35. data/spec/jira/base_spec.rb +555 -0
  36. data/spec/jira/client_spec.rb +12 -12
  37. data/spec/jira/has_many_proxy_spec.rb +45 -0
  38. data/spec/jira/{resource/http_error_spec.rb → http_error_spec.rb} +1 -1
  39. data/spec/jira/resource/attachment_spec.rb +20 -0
  40. data/spec/jira/resource/issue_spec.rb +83 -0
  41. data/spec/jira/resource/project_factory_spec.rb +3 -3
  42. data/spec/jira/resource/project_spec.rb +28 -0
  43. data/spec/jira/resource/worklog_spec.rb +24 -0
  44. data/spec/mock_responses/attachment/10000.json +20 -0
  45. data/spec/mock_responses/component/10000.invalid.put.json +5 -0
  46. data/spec/mock_responses/issue.json +1108 -0
  47. data/spec/mock_responses/issue/10002.invalid.put.json +6 -0
  48. data/spec/mock_responses/issue/10002.json +13 -1
  49. data/spec/mock_responses/issue/10002.put.missing_field_update.json +6 -0
  50. data/spec/mock_responses/issue/10002/comment.json +65 -0
  51. data/spec/mock_responses/issue/10002/comment.post.json +29 -0
  52. data/spec/mock_responses/issue/10002/comment/10000.json +29 -0
  53. data/spec/mock_responses/issue/10002/comment/10000.put.json +29 -0
  54. data/spec/mock_responses/issue/10002/worklog.json +98 -0
  55. data/spec/mock_responses/issue/10002/worklog.post.json +30 -0
  56. data/spec/mock_responses/issue/10002/worklog/10000.json +31 -0
  57. data/spec/mock_responses/issue/10002/worklog/10000.put.json +30 -0
  58. data/spec/mock_responses/issuetype.json +42 -0
  59. data/spec/mock_responses/issuetype/5.json +8 -0
  60. data/spec/mock_responses/priority.json +42 -0
  61. data/spec/mock_responses/priority/1.json +8 -0
  62. data/spec/mock_responses/project/SAMPLEPROJECT.issues.json +1108 -0
  63. data/spec/mock_responses/project/SAMPLEPROJECT.json +15 -1
  64. data/spec/mock_responses/status.json +37 -0
  65. data/spec/mock_responses/status/1.json +7 -0
  66. data/spec/mock_responses/user?username=admin.json +17 -0
  67. data/spec/mock_responses/version.post.json +7 -0
  68. data/spec/mock_responses/version/10000.invalid.put.json +5 -0
  69. data/spec/mock_responses/version/10000.json +11 -0
  70. data/spec/mock_responses/version/10000.put.json +7 -0
  71. data/spec/spec_helper.rb +7 -12
  72. data/spec/support/matchers/have_attributes.rb +11 -0
  73. data/spec/support/matchers/have_many.rb +9 -0
  74. data/spec/support/matchers/have_one.rb +5 -0
  75. data/spec/support/shared_examples/integration.rb +174 -0
  76. metadata +139 -24
  77. data/README.markdown +0 -81
  78. data/lib/jira/resource/base.rb +0 -148
  79. data/lib/jira/resource/base_factory.rb +0 -44
  80. data/lib/jira/resource/http_error.rb +0 -17
  81. data/spec/jira/resource/base_factory_spec.rb +0 -36
  82. data/spec/jira/resource/base_spec.rb +0 -292
@@ -0,0 +1,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