rodzilla 0.1.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZDUwNjdjOGNmNzE3ZWQzNDg2ZTE1MmVhZDFmNmUyYWU4N2RmM2ZkMA==
4
+ OGE5NzhhNGZkZDIyZWIwNWQxNTQ5MzI5YjRlYjliZjk3NDRkOWMwNw==
5
5
  data.tar.gz: !binary |-
6
- ODU5NjEwNDMyNGQ5YWIyNjViNTExOWVhYWYyMDJkY2JiNTYxN2EwYw==
6
+ NDJiZjc4MDI0YjE3M2E3MzQ3ZTU4ZWFjOTc2ZmY4MGM3MDgxZmEzNw==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- NTVkZmFhMzliYjcxM2RlNzdmMzU2YTE4NDRiMjJmYjViNmVlM2Q1NDlmMWRl
10
- OGNlNTA1MmU4MGU1OGExZDk2MjQyMWQ1N2MyOWY0YWZkMDNmY2VjODc1OGMz
11
- MDZlNzNkZDY3MWMyMzE0YTYyOWE4NWYyZmU2OGI5YTI5ZDU0ZWQ=
9
+ NTU4YzIyNjI0NDk4OTdkZGQ4ZjY4YjlkNzIwNWRmYzRhZDUzY2VlNmMyNmY4
10
+ MmJlMGVlMGIyMTAzZTA5ZWY5ZTk4MDIxYTg1ZDRkNTcxOTZjOWY3Mzc0MTU0
11
+ MjJhNzNkN2Q5ZjA0MDJiMGNkMzdkNmM5YjVhZjg1MDA2OWE0NDc=
12
12
  data.tar.gz: !binary |-
13
- NGM1ZTU1NWE4ZGVhZGVkNDI1MWZmNzc0ODYwMTdlZjMxZmUxMTBlNmNiZTdi
14
- M2I3Yjc0MTVjMTYxODZjZGI2ZTEzNjllZjI3NWMwZjQxZmIwYWMyZTdkOGUy
15
- OTRlNzZlYmQ3ZWJlOTJjY2MzNzU1ZThlOTBlYWI2MGUxOGRlZDA=
13
+ N2RhOGMyNmE2NjdkYWVlYzMzNDM5NDY0NGJjNDU0YWUxZjRjZWIxNGQ1ZDIx
14
+ ZmI2ZGZlYzUwNDY2Nzc0YTVkMzJmMzE1YWRlNGJiZDExNWMxZTY5YTI0OTAy
15
+ NTFkNDBiMmI1YmY2NDA3NTU2MzBiODhjN2U0ODFhZmJlNjc2N2I=
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/Gemfile CHANGED
@@ -1,4 +1,13 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem "rake"
4
+
5
+ group :test do
6
+ gem "minitest"
7
+ gem "mocha"
8
+ gem "simplecov", :require => false
9
+ gem "webmock"
10
+ end
11
+
3
12
  # Specify your gem's dependencies in rodzilla.gemspec
4
13
  gemspec
data/lib/rodzilla.rb CHANGED
@@ -1,53 +1,28 @@
1
1
  require "httparty"
2
2
  require "json"
3
- require "rodzilla/version"
4
- require "rodzilla/exception"
5
-
6
- # load resources
7
- require "rodzilla/resource/base"
8
- require "rodzilla/resource/product"
9
- require "rodzilla/resource/classification"
10
- require "rodzilla/resource/bugzilla"
11
- require "rodzilla/resource/bug"
12
3
 
13
4
  module Rodzilla
14
- class WebService
15
-
16
- attr_accessor :base_url, :username, :password, :format
17
-
18
- # @param base_url [String] the bugzilla full url
19
- # @param username [String] the bugzilla authorized users username
20
- # @param password [String] the bugzilla authorized users password
21
- # @param format [Symbol] the request/response format `:xml` or `:json`
22
- def initialize(base_url, username, password, format=:json)
23
- @base_url = base_url
24
- @username = username
25
- @password = password
26
- @format = format
27
- end
28
-
29
- def products
30
- bugzilla_resource('Product')
31
- end
32
-
33
- def bugzilla
34
- bugzilla_resource('Bugzilla')
35
- end
36
-
37
- def classifications
38
- bugzilla_resource('Classification')
39
- end
40
-
41
- def bugs
42
- bugzilla_resource('Bug')
43
- end
44
-
45
- protected
46
- def bugzilla_resource(resource)
47
- raise Rodzilla::ResourceNotFoundError, "Error: #{resource} not found." unless Rodzilla::Resource.constants.include?(resource.to_sym)
48
- klass = Object.module_eval("Rodzilla::Resource::#{resource}").new(@base_url, @username, @password, @format)
49
- end
50
-
5
+
6
+ autoload :VERSION, 'rodzilla/version'
7
+ autoload :Util, 'rodzilla/util'
8
+ autoload :Error, 'rodzilla/error'
9
+ autoload :WebService, 'rodzilla/web_service'
10
+
11
+ module JsonRpc
12
+ autoload :Error, 'rodzilla/json_rpc/error'
13
+ autoload :Service, 'rodzilla/json_rpc/service'
14
+ autoload :Request, 'rodzilla/json_rpc/request'
15
+ autoload :Response, 'rodzilla/json_rpc/response'
16
+ end
51
17
 
18
+ module Resource
19
+ autoload :Base, 'rodzilla/resource/base'
20
+ autoload :Bug, 'rodzilla/resource/bug'
21
+ autoload :Bugzilla, 'rodzilla/resource/bugzilla'
22
+ autoload :Classification, 'rodzilla/resource/classification'
23
+ autoload :Group, 'rodzilla/resource/group'
24
+ autoload :Product, 'rodzilla/resource/product'
25
+ autoload :User, 'rodzilla/resource/user'
52
26
  end
27
+
53
28
  end
@@ -0,0 +1,6 @@
1
+ module Rodzilla
2
+ module Error
3
+ class ResourceNotFoundError < StandardError; ;end
4
+ class JsonRpcResponseError < StandardError; ;end
5
+ end
6
+ end
@@ -0,0 +1,35 @@
1
+ module Rodzilla
2
+ module JsonRpc
3
+ module Error
4
+
5
+ # Raised if a JsonRpc Response id is not the same as the request id
6
+ class InvalidResponseId < StandardError; ;end
7
+
8
+ # Raised when any other method besides POST or GET is used
9
+ class UnsupportedHttpMethod < StandardError; ;end
10
+
11
+ # Raised whenever the JsonRpc::Response#error attribute is not nil
12
+ #
13
+ # Also inherited by Client & ServerError for handling HTTP Status Codes
14
+ class ResponseError < StandardError
15
+ attr_reader :code
16
+
17
+ def initialize(code)
18
+ @code = code
19
+ end
20
+
21
+ end
22
+
23
+ # Raised when the HTTP Response Status Code is in the 400 Range
24
+ class ClientError < ResponseError; ;end
25
+
26
+ # Raised when the HTTP Response Status Code is in the 500 Range
27
+ class ServerError < ResponseError; ;end
28
+
29
+ # Raised when something goes wrong with the raw HTTP Request
30
+ class HttpError < StandardError; ;end
31
+
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,24 @@
1
+ module Rodzilla
2
+ module JsonRpc
3
+ class Request
4
+ attr_accessor :id, :params, :method, :headers
5
+
6
+ def initialize(&block)
7
+ yield(self) if block_given?
8
+ end
9
+
10
+ def get_request_object
11
+ {
12
+ id: @id,
13
+ method: @method,
14
+ params: [@params]
15
+ }
16
+ end
17
+
18
+ def serialize
19
+ JSON.dump(get_request_object)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,20 @@
1
+ module Rodzilla
2
+ module JsonRpc
3
+ class Response
4
+ attr_accessor :result, :error, :id
5
+
6
+ def initialize
7
+ @error = nil
8
+ end
9
+
10
+ def read_http_response(http_response)
11
+ body = http_response.parsed_response
12
+ @id = body["id"]
13
+ @error = body["error"]
14
+ @result = body["result"]
15
+ self
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,94 @@
1
+ module Rodzilla
2
+ module JsonRpc
3
+
4
+ class Service
5
+
6
+ include HTTParty
7
+
8
+ attr_accessor :rpc_request, :rpc_response,
9
+ :username, :password, :credentials, :url
10
+
11
+ def initialize(url, username, password)
12
+ @url = url
13
+ @username = username
14
+ @password = password
15
+ @credentials = {
16
+ Bugzilla_login: @username,
17
+ Bugzilla_password: @password
18
+ }
19
+ setup_request
20
+ end
21
+
22
+ def send_request!(rpc_method, params={}, http_method=:post)
23
+ setup_request
24
+ setup_request_data(rpc_method, params)
25
+
26
+ unless ["post","get"].include?(http_method.to_s.downcase)
27
+ raise Rodzilla::JsonRpc::Error::UnsupportedHttpMethod, "Error: Only GET and POST request are supported HTTP methods."
28
+ end
29
+
30
+ execute_request_and_response(http_method)
31
+ end
32
+
33
+ def execute_request_and_response(http_method)
34
+ raw_request(http_method)
35
+ parse_http_response
36
+ raise Rodzilla::JsonRpc::Error::InvalidResponseId unless check_cycle_id
37
+ raise Rodzilla::JsonRpc::Error::ResponseError.new(@rpc_response.error["code"]), @rpc_response.error["message"] if @rpc_response.error
38
+ @rpc_response.result
39
+ end
40
+
41
+ private
42
+
43
+ def raw_request(http_method)
44
+ begin
45
+ @http_response = self.class.send(http_method, @url, body: rpc_request.serialize, headers: rpc_request.headers )
46
+ rescue => ex
47
+ raise Rodzilla::JsonRpc::Error::HttpError, ex.message
48
+ end
49
+
50
+ case @http_response.code
51
+ when 400...500
52
+ raise Rodzilla::JsonRpc::Error::ClientError
53
+ when 500...600
54
+ raise Rodzilla::JsonRpc::Error::ServerError
55
+ end
56
+
57
+ end
58
+
59
+ # Sets the Content-Type to application/json-rpc for the Service
60
+ # also adds Bugzilla defined login/password to the request parameters
61
+ #
62
+ # sets id for the request which is later checked using check_cycle_id
63
+ #
64
+ # Return
65
+ def setup_request
66
+ @rpc_request = Rodzilla::JsonRpc::Request.new do |request|
67
+ request.headers = { 'Content-Type' => 'application/json-rpc' }
68
+ request.id = generate_cycle_id
69
+ end
70
+ end
71
+
72
+ def setup_request_data(rpc_method, params={})
73
+ @rpc_request.method = rpc_method
74
+ @rpc_request.params = @credentials.merge(params)
75
+ end
76
+
77
+ # TODO: Consider replacing with Random.rand(1..N) for increased security
78
+ def generate_cycle_id; 1; end
79
+
80
+ def check_cycle_id
81
+ return false if rpc_request.id != rpc_response.id
82
+ true
83
+ end
84
+
85
+ def parse_http_response
86
+ @rpc_response = Rodzilla::JsonRpc::Response.new
87
+ @rpc_response.read_http_response(@http_response)
88
+ @rpc_response
89
+ end
90
+
91
+ end
92
+
93
+ end
94
+ end
@@ -1,71 +1,48 @@
1
1
  module Rodzilla
2
2
  module Resource
3
3
  class Base
4
- @@_request_id = 1
5
- include HTTParty
6
- attr_accessor :base_url, :username, :password, :format,
7
- :request_url, :response, :result, :credentials, :headers
4
+ attr_accessor :base_url, :username, :password, :service, :format
8
5
 
9
6
  def initialize(base_url, username, password, format=:json)
10
7
  @base_url = base_url
11
8
  @username = username
12
9
  @password = password
13
- @format = format
14
- @credentials = {
15
- Bugzilla_login: @username,
16
- Bugzilla_password: @password
17
- }
18
- @headers = { "Content-Type" => 'application/json-rpc' }
10
+ @format = format
11
+
12
+ setup_service(format)
19
13
  end
20
14
 
21
-
15
+ def rpc_call(rpc_method_name, params={}, http_method=:post)
16
+ service.send_request!( get_resource_rpc_method_name(rpc_method_name), params, http_method)
17
+ end
22
18
 
23
19
  protected
24
20
 
25
- def prepare_request(params={})
26
- @request_url = build_url
27
- @params = build_params(params)
28
- end
29
-
30
- def build_url
31
- File.join("#{@base_url}", "#{@format}rpc.cgi")
32
- end
33
-
34
- def build_params(params={})
35
- rpc_method = params.delete(:rpc_method)
36
- { params: [@credentials.merge(params)], method: "#{self.class.demodulize(self.class)}.#{rpc_method}", id: make_id }
21
+ def get_resource_rpc_method_name(rpc_method_name)
22
+ [Rodzilla::Util.demodulize(self.class), rpc_method_name.to_s].join('.')
37
23
  end
38
24
 
39
- def self.demodulize(path)
40
- path = path.to_s
41
- if i = path.rindex('::')
42
- path[(i+2)..-1]
43
- else
44
- path
25
+ def setup_service(format)
26
+ case format
27
+ when :json
28
+ @endpoint_url = set_endpoint_url(:json)
29
+ @service = Rodzilla::JsonRpc::Service.new(@endpoint_url, @username, @password)
45
30
  end
46
31
  end
47
32
 
48
- def make_id
49
- @@_request_id += 1
50
- end
51
-
52
- def rpc_call(params={})
53
- prepare_request( params )
54
- @response = self.class.post(@request_url, body: JSON.dump(@params), headers: @headers )
55
- parse_rpc_response!
33
+ def set_endpoint_url(format)
34
+ case format
35
+ when :json
36
+ File.join(@base_url, 'jsonrpc.cgi')
37
+ end
56
38
  end
57
39
 
58
- def parse_rpc_response!
59
- begin
60
- res = @response.parsed_response
61
- if err = res["error"]
62
- raise Rodzilla::JsonRpcResponseError, "Error (#{err['code']}): #{err['message']}"
63
- end
64
- @result = res["result"]
65
- rescue => ex
66
- raise ex
40
+ def check_params(required=[], actual)
41
+ if actual.is_a?(Hash)
42
+ (required - actual.keys).length == 0
43
+ else
44
+ (required - actual).length == 0
67
45
  end
68
- @result
69
46
  end
70
47
 
71
48
  end
@@ -1,37 +1,107 @@
1
1
  module Rodzilla
2
2
  module Resource
3
3
  class Bug < Base
4
- REQUIRED_FIELDS = [:product, :component, :summary, :version, :description, :op_sys, :platform, :priority, :severity]
5
4
 
5
+ # Get information about valid bug fields, including the lists of legal values for each field.
6
+ #
7
+ # ids (array) - An array of integer field ids.
8
+ # names (array) - An array of strings representing field names.
9
+ #
10
+ # Support: >= 3.6
11
+ #
12
+ # If neither ids nor names is specified, then all non-obsolete fields will be returned.
6
13
  def fields(params={})
7
- rpc_call( params.merge(rpc_method: "fields"))
8
- end
9
-
10
- # Returns all comments associated with a Bug
11
- def comments(params={})
12
- raise ArgumentError, "Error: comments requires that bug_id be set" unless params[:ids]
14
+ rpc_call :fields, params
13
15
  end
14
16
 
17
+ # Allows you to search for bugs based on particular criteria.
18
+ #
15
19
  # Unless otherwise specified in the description of a parameter,
16
20
  # bugs are returned if they match exactly the criteria you specify in these parameters.
17
21
  # That is, we don't match against substrings--if a bug is in the "Widgets" product and you ask for bugs in the "Widg" product, you won't get anything.
18
22
  #
19
- # Criteria are joined in a logical AND.
20
- # That is, you will be returned bugs that match all of the criteria, not bugs that match any of the criteria.
23
+ # Criteria are joined in a logical AND
24
+ #
25
+ # alias - string The unique alias for this bug.
26
+ # assigned_to - string The login name of a user that a bug is assigned to.
27
+ # component - string The name of the Component that the bug is in. Note that if there are multiple Components with the same name, and you search for that name, bugs in all those Components will be returned. If you don't want this, be sure to also specify the product argument.
28
+ # creation_time - dateTime Searches for bugs that were created at this time or later. May not be an array.
29
+ # creator - string The login name of the user who created the bug.
30
+ # id - int The numeric id of the bug.
31
+ # last_change_time - dateTime Searches for bugs that were modified at this time or later. May not be an array.
32
+ # limit - int Limit the number of results returned to int records. If the limit is more than zero and higher than the maximum limit set by the administrator, then the maximum limit will be used instead. If you set the limit equal to zero, then all matching results will be returned instead.
33
+ # offset - int Used in conjunction with the limit argument, offset defines the starting position for the search. For example, given a search that would return 100 bugs, setting limit to 10 and offset to 10 would return bugs 11 through 20 from the set of 100.
34
+ # op_sys - string The "Operating System" field of a bug.
35
+ # platform -string The Platform (sometimes called "Hardware") field of a bug.
36
+ # priority - string The Priority field on a bug.
37
+ # product - string The name of the Product that the bug is in.
38
+ # resolution - string The current resolution--only set if a bug is closed. You can find open bugs by searching for bugs with an empty resolution.
39
+ # severity - string The Severity field on a bug.
40
+ # status - string The current status of a bug (not including its resolution, if it has one, which is a separate field above).
41
+ # summary - string Searches for substrings in the single-line Summary field on bugs. If you specify an array, then bugs whose summaries match any of the passed substrings will be returned.
42
+ # target_milestone - string The Target Milestone field of a bug. Note that even if this Bugzilla does not have the Target Milestone field enabled, you can still search for bugs by Target Milestone. However, it is likely that in that case, most bugs will not have a Target Milestone set (it defaults to "---" when the field isn't enabled).
43
+ # qa_contact - string The login name of the bug's QA Contact. Note that even if this Bugzilla does not have the QA Contact field enabled, you can still search for bugs by QA Contact (though it is likely that no bug will have a QA Contact set, if the field is disabled).
44
+ # url - string The "URL" field of a bug.
45
+ # version
46
+ #
47
+ # All params can be either the type or an array of the types
48
+ def search(params={})
49
+ rpc_call :search, params
50
+ end
51
+
52
+ # It allows you to get data about attachments, given a list of bugs and/or attachment ids.
53
+ # Note: Private attachments will only be returned if you are in the insidergroup or if you are the submitter of the attachment.
54
+ #
55
+ # ids (Array) - An array of integer field ids.
56
+ # attachment_ids (Array) - An array of integer attachment ids.
57
+ #
58
+ # Support: >= 3.6
59
+ #
60
+ # Returns a Hash containing two elements: bugs and attachments
61
+ def attachments(params={})
62
+ raise ArgumentError, "Error: ids or attachment_ids must be set" unless params[:ids] || params[:attachment_ids]
63
+ rpc_call :attachments, params
64
+ end
65
+
66
+ # Gets information about particular bugs in the database.
67
+ #
68
+ # ids (Array) - An array of numbers and strings. If an element in the array is entirely numeric,
69
+ # it represents a bug_id from the Bugzilla database to fetch. If it contains any non-numeric characters,
70
+ # it is considered to be a bug alias instead, and the bug with that alias will be loaded.
71
+ # permissive (Boolean) - Normally, if you request any inaccessible or invalid bug ids, Bug.get will throw an error. If true it returns info
72
+ # about bugs that fail.
73
+ #
21
74
  #
22
- # Each parameter can be either the type it says, or an array of the types it says.
23
- # If you pass an array, it means "Give me bugs with any of these values."
24
- # For example, if you wanted bugs that were in either the "Foo" or "Bar" products, you'd pass:
75
+ # Support: >= 3.4
76
+ #
77
+ # Returns a Hash containing two elements: bugs and faults
78
+ def get(params={})
79
+ rpc_call :get, params
80
+ end
81
+
82
+ # Same as get
25
83
  #
26
- # Valid values:
27
- # product, url, id, alias, limit
28
- def search(query={})
29
- rpc_call( query.merge(rpc_method: 'search') )
84
+ # Backwards compat with v3.0
85
+ def get_bugs(params={})
86
+ rpc_call :get_bugs, params
30
87
  end
31
88
 
32
- # see: http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bug.html#create
89
+
90
+ # This allows you to get data about comments, given a list of bugs and/or comment ids.
91
+ #
92
+ # ids (Array) - An array of integer field ids.
93
+ # comment_ids (Array) - An array of integer field ids.
33
94
  #
34
- # Required Fields:
95
+ # Support: >= 3.4
96
+ #
97
+ # Returns a Hash containing two items: bugs and comments
98
+ def comments(params={})
99
+ raise ArgumentError, "Error: ids or comment_ids must be set" unless params[:ids] || params[:comment_ids]
100
+ rpc_call :comments, params
101
+ end
102
+
103
+ # This allows you to create a new bug in Bugzilla.
104
+ #
35
105
  # product (string) Required - The name of the product the bug is being filed against.
36
106
  # component (string) Required - The name of a component in the product above.
37
107
  # summary (string) Required - A brief description of the bug being filed.
@@ -41,17 +111,51 @@ module Rodzilla
41
111
  # platform (string) Defaulted - What type of hardware the bug was experienced on.
42
112
  # priority (string) Defaulted - What order the bug will be fixed in by the developer, compared to the developer's other bugs.
43
113
  # severity (string) Defaulted - How severe the bug is.
44
- #
45
- # Raises an exception if a required field is not provided
46
114
  def create!(params={})
47
- REQUIRED_FIELDS.each { |field| raise ArgumentError, "Error: #{field} is required" unless params[field] }
48
- rpc_call( params.merge(rpc_method: 'create' ))
115
+ raise ArgumentError, "Error: product, component, summary, version are required args" unless check_params([:product, :component, :summary, :version], params)
116
+ rpc_call :create, params
49
117
  end
50
118
 
119
+ # Same as create! but does not raise an ArgumentError for required arguments
51
120
  def create(params={})
52
- rpc_call( params.merge(rpc_method: 'create'))
121
+ rpc_call :create, params
122
+ end
123
+
124
+ # This allows you to add an attachment to a bug in Bugzilla.
125
+ #
126
+ # ids - (Required) An array of ints and/or strings--the ids or aliases of bugs that you want to add this attachment to. The same attachment and comment will be added to all these bugs.
127
+ # data - (Required) base64 or string The content of the attachment. If the content of the attachment is not ASCII text, you must encode it in base64 and declare it as the base64 type.
128
+ # file_name - (Required) string The "file name" that will be displayed in the UI for this attachment.
129
+ # summary - (Required) string A short string describing the attachment.
130
+ # content_type - (Required) string The MIME type of the attachment, like text/plain or image/png.
131
+ #
132
+ # comment - String A comment to add along with this attachment.
133
+ # is_patch Boolean true if Bugzilla should treat this attachment as a patch
134
+ # is_private Boolean true if the attachment should be private
135
+ #
136
+ # Support: >= 4.0
137
+ #
138
+ # Returns a single item ids, which contains an array of the attachment id(s) created.
139
+ def add_attachment!(params={})
140
+ raise ArgumentError, "Error: ids, data, file_name, summary, and content_type are required args" unless check_params([:ids, :data, :file_name, :summary, :content_type], params)
141
+ rpc_call :add_attachment, params
142
+ end
143
+
144
+ # Same as add_attachment! but won't raise an exception for missing required arguments
145
+ def add_attachment(params={})
146
+ rcp_call :add_attachment, params
53
147
  end
54
-
148
+
149
+ # Allows you to update the fields of a bug. Automatically sends emails out about the changes.
150
+ #
151
+ # ids - Array of ints or strings. The ids or aliases of the bugs that you want to modify.
152
+ #
153
+ # Returns a Hash with a single field, "bugs".
154
+ def update(params={})
155
+ raise ArgumentError, "Error: ids is a required arg" unless check_params([:ids], params)
156
+ rpc_call :update, params
157
+ end
158
+
55
159
  end
56
160
  end
57
161
  end