active_bugzilla 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +23 -0
- data/README.md +57 -0
- data/lib/active_bugzilla.rb +12 -0
- data/lib/active_bugzilla/base.rb +21 -0
- data/lib/active_bugzilla/bug.rb +107 -0
- data/lib/active_bugzilla/bug/flags_management.rb +88 -0
- data/lib/active_bugzilla/bug/service_management.rb +174 -0
- data/lib/active_bugzilla/comment.rb +23 -0
- data/lib/active_bugzilla/field.rb +68 -0
- data/lib/active_bugzilla/flag.rb +22 -0
- data/lib/active_bugzilla/service.rb +234 -0
- data/lib/active_bugzilla/version.rb +3 -0
- data/spec/active_bugzilla/bug_spec.rb +45 -0
- data/spec/active_bugzilla/comment_spec.rb +41 -0
- data/spec/active_bugzilla/service_spec.rb +165 -0
- data/spec/active_bugzilla_spec.rb +7 -0
- data/spec/spec_helper.rb +24 -0
- metadata +187 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2013-2014 Red Hat, Inc.
|
2
|
+
|
3
|
+
|
4
|
+
MIT License
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
7
|
+
a copy of this software and associated documentation files (the
|
8
|
+
"Software"), to deal in the Software without restriction, including
|
9
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
10
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
11
|
+
permit persons to whom the Software is furnished to do so, subject to
|
12
|
+
the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
19
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
21
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
22
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
23
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# ActiveBugzilla
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/active_bugzilla.png)](http://badge.fury.io/rb/active_bugzilla)
|
4
|
+
[![Build Status](https://travis-ci.org/ManageIQ/active_bugzilla.png)](https://travis-ci.org/ManageIQ/active_bugzilla)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/ManageIQ/active_bugzilla.png)](https://codeclimate.com/github/ManageIQ/active_bugzilla)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/ManageIQ/active_bugzilla/badge.png?branch=master)](https://coveralls.io/r/ManageIQ/active_bugzilla)
|
7
|
+
[![Dependency Status](https://gemnasium.com/ManageIQ/active_bugzilla.png)](https://gemnasium.com/ManageIQ/active_bugzilla)
|
8
|
+
|
9
|
+
ActiveBugzilla is an ActiveRecord like interface to the Bugzilla API.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'active_bugzilla'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install active_bugzilla
|
24
|
+
|
25
|
+
## Example Usage
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
service = ActiveBugzilla::Service.new("http://uri.to/bugzilla", username, password)
|
29
|
+
ActiveBugzilla::Base.service = service
|
30
|
+
bugs = ActiveBugzilla::Bug.find(:product => product_name, :status => "NEW")
|
31
|
+
bugs.each do |bug|
|
32
|
+
puts "Bug ##{bug.id} - created_on=#{bug.created_on}, updated_on=#{bug.updated_on}, priority=#{bug.priority}"
|
33
|
+
puts "Bug Attributes: #{bug.attribute_names.inspect}"
|
34
|
+
end
|
35
|
+
|
36
|
+
bug = ActiveBugzilla::Bug.find(:id => 12345)
|
37
|
+
puts "PRIORITY: #{bug.priority}" # => "low"
|
38
|
+
puts "FLAGS: #{bug.flags.inspect}" # => {"devel_ack"=>"?", "qa_ack"=>"+"}
|
39
|
+
bug.priority = "high"
|
40
|
+
bug.flags.delete("qa_ack")
|
41
|
+
bug.flags["devel_ack"] = "+"
|
42
|
+
bug.save
|
43
|
+
puts "PRIORITY: #{bug.priority}" # => "high"
|
44
|
+
puts "FLAGS: #{bug.flags.inspect}" # => {"devel_ack"=>"+"}
|
45
|
+
puts "FLAG OBJECTS: #{bug.flag_objects.inspect}" # => Array of ActiveBugzilla:Flag objects
|
46
|
+
|
47
|
+
bug.add_comment("Testing")
|
48
|
+
puts "COMMENTS: #{bug.comments.inspect}" # => Array of ActiveBugzilla:Comment objects
|
49
|
+
```
|
50
|
+
|
51
|
+
## Contributing
|
52
|
+
|
53
|
+
1. Fork it
|
54
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
55
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
56
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
57
|
+
5. Create new Pull Request
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'active_bugzilla/version'
|
2
|
+
|
3
|
+
require 'active_bugzilla/service'
|
4
|
+
|
5
|
+
require 'active_bugzilla/base'
|
6
|
+
require 'active_bugzilla/bug'
|
7
|
+
require 'active_bugzilla/comment'
|
8
|
+
require 'active_bugzilla/field'
|
9
|
+
require 'active_bugzilla/flag'
|
10
|
+
|
11
|
+
module ActiveBugzilla
|
12
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveBugzilla
|
2
|
+
class Base
|
3
|
+
def self.service=(service)
|
4
|
+
@@service = service
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.service
|
8
|
+
@@service
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def self.normalize_timestamp(timestamp)
|
14
|
+
timestamp.respond_to?(:to_time) ? timestamp.to_time : nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def normalize_timestamp(timestamp)
|
18
|
+
self.class.normalize_timestamp(timestamp)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module ActiveBugzilla
|
4
|
+
class Bug < Base
|
5
|
+
include ActiveModel::Validations
|
6
|
+
include ActiveModel::Dirty
|
7
|
+
|
8
|
+
require_relative 'bug/service_management'
|
9
|
+
include ServiceManagement
|
10
|
+
|
11
|
+
require_relative 'bug/flags_management'
|
12
|
+
include FlagsManagement
|
13
|
+
|
14
|
+
validates_numericality_of :id
|
15
|
+
|
16
|
+
def initialize(attributes = {})
|
17
|
+
attributes.each do |key, value|
|
18
|
+
next unless attribute_names.include?(key)
|
19
|
+
ivar_key = "@#{key}"
|
20
|
+
instance_variable_set(ivar_key, value)
|
21
|
+
end if attributes
|
22
|
+
end
|
23
|
+
|
24
|
+
def save
|
25
|
+
return if changes.empty?
|
26
|
+
update_attributes(changed_attribute_hash)
|
27
|
+
@changed_attributes.clear
|
28
|
+
reload
|
29
|
+
end
|
30
|
+
|
31
|
+
def reload
|
32
|
+
raw_reset
|
33
|
+
reset_instance_variables
|
34
|
+
reset_flags
|
35
|
+
@comments = Comment.instantiate_from_raw_data(raw_comments)
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_attributes(attributes)
|
40
|
+
attributes.delete(:id)
|
41
|
+
|
42
|
+
attributes.each do |name, value|
|
43
|
+
symbolized_name = name.to_sym
|
44
|
+
raise "Unknown Attribute #{name}" unless attribute_names.include?(symbolized_name)
|
45
|
+
public_send("#{name}=", value)
|
46
|
+
if symbolized_name == :flags
|
47
|
+
attributes[name] = flags_raw_updates
|
48
|
+
else
|
49
|
+
@changed_attributes.delete(symbolized_name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
raw_update(attributes) unless attributes.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_attribute(key, value)
|
57
|
+
update_attributes(key => value)
|
58
|
+
end
|
59
|
+
|
60
|
+
def comments
|
61
|
+
@comments ||= Comment.instantiate_from_raw_data(raw_comments)
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_comment(comment, is_private = false)
|
65
|
+
_comment_id = service.add_comment(@id, comment, :is_private => is_private)
|
66
|
+
reload
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.fields
|
70
|
+
@fields ||= Field.instantiate_from_raw_data(fetch_fields)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.find(options = {})
|
74
|
+
options[:include_fields] ||= []
|
75
|
+
options[:include_fields] << :id unless options[:include_fields].include?(:id)
|
76
|
+
|
77
|
+
fields_to_include = options[:include_fields].dup
|
78
|
+
|
79
|
+
search(options).collect do |bug_hash|
|
80
|
+
fields_to_include.each do |field|
|
81
|
+
bug_hash[field] = nil unless bug_hash.key?(field)
|
82
|
+
bug_hash[field] = flags_from_raw_flags_data(bug_hash[field]) if field == :flags
|
83
|
+
end
|
84
|
+
Bug.new(bug_hash)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def reset_instance_variables
|
91
|
+
attribute_names do |name|
|
92
|
+
next if name == :id
|
93
|
+
ivar_name = "@#{name}"
|
94
|
+
instance_variable_set(ivar_name, raw_attribute(name))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def changed_attribute_hash
|
99
|
+
hash = {}
|
100
|
+
changes.each do |key, values|
|
101
|
+
_value_from, value_to = values
|
102
|
+
hash[key.to_sym] = value_to
|
103
|
+
end
|
104
|
+
hash
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'dirty_hashy'
|
3
|
+
|
4
|
+
module ActiveBugzilla::Bug::FlagsManagement
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def flags_from_raw_flags_data(raw_flags_data)
|
9
|
+
return {} if raw_flags_data.nil?
|
10
|
+
flag_objects = ActiveBugzilla::Flag.instantiate_from_raw_data(raw_flags_data)
|
11
|
+
flag_objects.each_with_object({}) do |flag, hash|
|
12
|
+
hash[flag.name] = flag.status
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def flag_objects
|
18
|
+
@flag_objects ||= ActiveBugzilla::Flag.instantiate_from_raw_data(raw_flags, @id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def flags=(value)
|
22
|
+
flags_will_change! unless value == @flags
|
23
|
+
@flags = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def flags
|
27
|
+
@flags ||= begin
|
28
|
+
flags_hash = flag_objects.each_with_object(DirtyIndifferentHashy.new) do |flag, hash|
|
29
|
+
hash[flag.name] = flag.status
|
30
|
+
end
|
31
|
+
flags_hash.clean_up!
|
32
|
+
flags_hash
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def flags_raw_updates
|
37
|
+
raw_updates = []
|
38
|
+
flags.changes.each do |key, value|
|
39
|
+
_old_status, new_status = value
|
40
|
+
new_status ||= 'X'
|
41
|
+
raw_updates << {'name' => key.to_s, "status" => new_status}
|
42
|
+
end
|
43
|
+
raw_updates
|
44
|
+
end
|
45
|
+
|
46
|
+
def reset_flags
|
47
|
+
@flag_objects = nil
|
48
|
+
@flags = nil
|
49
|
+
flags
|
50
|
+
end
|
51
|
+
|
52
|
+
def changed_with_flags?
|
53
|
+
changed_without_flags? || flags.changed?
|
54
|
+
end
|
55
|
+
|
56
|
+
def changes_with_flags
|
57
|
+
changes = changes_without_flags
|
58
|
+
changes['flags'] = [flags_previous_value, flags] if flags.changed?
|
59
|
+
changes
|
60
|
+
end
|
61
|
+
|
62
|
+
def flags_previous_value
|
63
|
+
previous_flags = flags.dup
|
64
|
+
flags.changes.each do |key, value|
|
65
|
+
previous_flags[key] = value.first
|
66
|
+
end
|
67
|
+
previous_flags
|
68
|
+
end
|
69
|
+
|
70
|
+
def changed_attributes_with_flags
|
71
|
+
changed_attributes = changed_attributes_without_flags
|
72
|
+
changed_attributes['flags'] = flags_previous_value if flags.changed?
|
73
|
+
changed_attributes
|
74
|
+
end
|
75
|
+
|
76
|
+
included do
|
77
|
+
define_attribute_methods [:flags]
|
78
|
+
|
79
|
+
alias_method :changed_without_flags?, :changed?
|
80
|
+
alias_method :changed?, :changed_with_flags?
|
81
|
+
|
82
|
+
alias_method :changes_without_flags, :changes
|
83
|
+
alias_method :changes, :changes_with_flags
|
84
|
+
|
85
|
+
alias_method :changed_attributes_without_flags, :changed_attributes
|
86
|
+
alias_method :changed_attributes, :changed_attributes_with_flags
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
module ActiveBugzilla::Bug::ServiceManagement
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
ATTRIBUTES_XMLRPC_RENAMES_MAP = {
|
6
|
+
# Bug => XMLRPC
|
7
|
+
:created_by => :creator,
|
8
|
+
:created_on => :creation_time,
|
9
|
+
:duplicate_id => :dupe_of,
|
10
|
+
:updated_on => :last_change_time,
|
11
|
+
|
12
|
+
# Some are absent from what Bugzilla.fields() returns
|
13
|
+
:actual_time => :actual_time,
|
14
|
+
:flags => :flags,
|
15
|
+
}
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def attributes_xmlrpc_map
|
19
|
+
@attributes_xmlrpc_map ||= begin
|
20
|
+
hash = generate_xmlrpc_map
|
21
|
+
define_attributes(hash.keys)
|
22
|
+
hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def xmlrpc_timestamps
|
27
|
+
@xmlrpc_timestamps ||= fields.select(&:timestamp?).collect { |field| field.name.to_sym }
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_service_attributes
|
31
|
+
attributes_xmlrpc_map.values - [:comments]
|
32
|
+
end
|
33
|
+
|
34
|
+
def normalize_attributes_to_service(hash)
|
35
|
+
attributes_xmlrpc_map.each do |bug_key, xmlrpc_key|
|
36
|
+
bug_key = bug_key.to_sym
|
37
|
+
xmlrpc_key = xmlrpc_key.to_sym
|
38
|
+
next if bug_key == xmlrpc_key
|
39
|
+
hash[xmlrpc_key] = hash.delete(bug_key)
|
40
|
+
end
|
41
|
+
|
42
|
+
hash[:include_fields] = normalize_include_fields_to_service(hash[:include_fields]) if hash.key?(:include_fields)
|
43
|
+
|
44
|
+
hash.delete_if { |k, v| v.nil? }
|
45
|
+
hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def normalize_attributes_from_service(hash)
|
49
|
+
attributes_xmlrpc_map.each do |bug_key, xmlrpc_key|
|
50
|
+
next unless hash.key?(xmlrpc_key.to_s)
|
51
|
+
value = hash.delete(xmlrpc_key.to_s)
|
52
|
+
value = normalize_timestamp(value) if xmlrpc_timestamps.include?(xmlrpc_key)
|
53
|
+
hash[bug_key] = value
|
54
|
+
end
|
55
|
+
|
56
|
+
hash
|
57
|
+
end
|
58
|
+
|
59
|
+
def attribute_names
|
60
|
+
@attribute_names ||= attributes_xmlrpc_map.keys.sort_by { |sym| sym.to_s }
|
61
|
+
end
|
62
|
+
|
63
|
+
def search(options = {})
|
64
|
+
options = normalize_attributes_to_service(options)
|
65
|
+
service.search(options).collect do |bug_hash|
|
66
|
+
normalize_attributes_from_service(bug_hash)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def fetch_fields
|
73
|
+
service.fields
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_xmlrpc_map
|
77
|
+
hash = ATTRIBUTES_XMLRPC_RENAMES_MAP
|
78
|
+
fields.each do |field|
|
79
|
+
next if hash.values.include?(field.name)
|
80
|
+
next if field.name.include?(".")
|
81
|
+
attribute_name = field.name
|
82
|
+
attribute_name = attribute_name[3..-1] if attribute_name[0..2] == "cf_"
|
83
|
+
hash[attribute_name.to_sym] = field.name.to_sym
|
84
|
+
end
|
85
|
+
hash
|
86
|
+
end
|
87
|
+
|
88
|
+
def normalize_include_fields_to_service(include_fields)
|
89
|
+
include_fields.collect do |bug_key|
|
90
|
+
attributes_xmlrpc_map[bug_key]
|
91
|
+
end.uniq.compact
|
92
|
+
end
|
93
|
+
|
94
|
+
def define_attributes(names)
|
95
|
+
define_attribute_methods names
|
96
|
+
|
97
|
+
names.each do |name|
|
98
|
+
next if name.to_s == 'flags' # Flags is a special attribute
|
99
|
+
|
100
|
+
ivar_name = "@#{name}"
|
101
|
+
define_method(name) do
|
102
|
+
return instance_variable_get(ivar_name) if instance_variable_defined?(ivar_name)
|
103
|
+
instance_variable_set(ivar_name, raw_attribute(name))
|
104
|
+
end
|
105
|
+
|
106
|
+
define_method("#{name}=") do |val|
|
107
|
+
public_send("#{name}_will_change!") unless val == instance_variable_get(ivar_name)
|
108
|
+
instance_variable_set(ivar_name, val)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def attribute_names
|
115
|
+
self.class.attribute_names
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def service
|
121
|
+
self.class.service
|
122
|
+
end
|
123
|
+
|
124
|
+
def raw_reset
|
125
|
+
@raw_data = nil
|
126
|
+
@raw_comments = nil
|
127
|
+
@raw_flags = nil
|
128
|
+
@raw_attributes = nil
|
129
|
+
end
|
130
|
+
|
131
|
+
def raw_update(attributes)
|
132
|
+
attributes = self.class.normalize_attributes_to_service(attributes)
|
133
|
+
result = service.update(@id, attributes).first
|
134
|
+
|
135
|
+
id = result['id']
|
136
|
+
raise "Error - Expected to update id <#{@id}>, but updated <#{id}>" unless id == @id
|
137
|
+
|
138
|
+
result
|
139
|
+
end
|
140
|
+
|
141
|
+
def raw_data
|
142
|
+
@raw_data ||= service.get(@id, :include_fields => self.class.default_service_attributes).first
|
143
|
+
end
|
144
|
+
|
145
|
+
def raw_flags
|
146
|
+
@raw_flags ||= raw_attribute('flags')
|
147
|
+
end
|
148
|
+
|
149
|
+
def raw_comments
|
150
|
+
@raw_comments ||= (raw_attributes['comments'] || fetch_comments)
|
151
|
+
end
|
152
|
+
|
153
|
+
def raw_attributes
|
154
|
+
@raw_attributes ||= self.class.normalize_attributes_from_service(raw_data)
|
155
|
+
end
|
156
|
+
|
157
|
+
def raw_attribute_set(key, value)
|
158
|
+
raw_attributes
|
159
|
+
@raw_attributes[key] = value
|
160
|
+
end
|
161
|
+
|
162
|
+
def raw_attribute(key)
|
163
|
+
raw_attribute_set(key, fetch_attribute(key)) unless raw_attributes.key?(key)
|
164
|
+
raw_attributes[key]
|
165
|
+
end
|
166
|
+
|
167
|
+
def fetch_comments
|
168
|
+
service.comments(:ids => @id)['bugs'][@id.to_s]['comments']
|
169
|
+
end
|
170
|
+
|
171
|
+
def fetch_attribute(key)
|
172
|
+
service.get(@id, :include_fields => [key]).first[key]
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveBugzilla
|
2
|
+
class Comment < Base
|
3
|
+
attr_reader :bug_id, :count, :created_by, :created_on, :creator_id, :id, :private, :text, :updated_on
|
4
|
+
alias_method :private?, :private
|
5
|
+
|
6
|
+
def initialize(attributes)
|
7
|
+
@created_by = attributes['author']
|
8
|
+
@bug_id = attributes['bug_id']
|
9
|
+
@count = attributes['count']
|
10
|
+
@creator_id = attributes['creator_id']
|
11
|
+
@id = attributes['id']
|
12
|
+
@text = attributes['text']
|
13
|
+
|
14
|
+
@created_on = normalize_timestamp attributes['creation_time']
|
15
|
+
@updated_on = normalize_timestamp attributes['time']
|
16
|
+
@private = attributes['is_private']
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.instantiate_from_raw_data(data)
|
20
|
+
data.sort_by(&:count).collect { |hash| new(hash) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module ActiveBugzilla
|
2
|
+
class Field < Base
|
3
|
+
attr_reader :display_name, :id, :name, :original_name, :type, :values, :visibility_field, :visibility_values
|
4
|
+
attr_reader :is_custom, :is_mandatory, :is_on_bug_entry
|
5
|
+
alias_method :mandatory?, :is_mandatory
|
6
|
+
alias_method :custom?, :is_custom
|
7
|
+
alias_method :on_bug_entry?, :is_on_bug_entry
|
8
|
+
|
9
|
+
KNOWN_TIMESTAMPS = %w(creation_time last_change_time)
|
10
|
+
|
11
|
+
# List of field aliases. Maps old style RHBZ parameter names to actual
|
12
|
+
# upstream values. Used for createbug() and query include_fields at
|
13
|
+
# least.
|
14
|
+
FIELD_ALIASES = {
|
15
|
+
# old => current
|
16
|
+
'short_desc' => 'summary',
|
17
|
+
'comment' => 'description',
|
18
|
+
'rep_platform' => 'platform',
|
19
|
+
'bug_severity' => 'severity',
|
20
|
+
'bug_status' => 'status',
|
21
|
+
'bug_id' => 'id',
|
22
|
+
'blockedby' => 'blocks',
|
23
|
+
'blocked' => 'blocks',
|
24
|
+
'dependson' => 'depends_on',
|
25
|
+
'reporter' => 'creator',
|
26
|
+
'bug_file_loc' => 'url',
|
27
|
+
'dupe_id' => 'dupe_of',
|
28
|
+
'dup_id' => 'dupe_of',
|
29
|
+
'longdescs' => 'comments',
|
30
|
+
'opendate' => 'creation_time',
|
31
|
+
'creation_ts' => 'creation_time',
|
32
|
+
'status_whiteboard' => 'whiteboard',
|
33
|
+
'delta_ts' => 'last_change_time',
|
34
|
+
}
|
35
|
+
|
36
|
+
def initialize(attributes = {})
|
37
|
+
@display_name = attributes["display_name"]
|
38
|
+
@id = attributes["id"]
|
39
|
+
@name = self.class.field_alias(attributes["name"])
|
40
|
+
@original_name = attributes["name"]
|
41
|
+
@type = attributes["type"]
|
42
|
+
@values = attributes["values"]
|
43
|
+
@visibility_field = attributes["visibility_field"]
|
44
|
+
@visibility_values = attributes["visibility_values"]
|
45
|
+
@is_custom = attributes["is_custom"]
|
46
|
+
@is_mandatory = attributes["is_mandatory"]
|
47
|
+
@is_on_bug_entry = attributes["is_on_bug_entry"]
|
48
|
+
end
|
49
|
+
|
50
|
+
def timestamp?
|
51
|
+
(type == 5) || KNOWN_TIMESTAMPS.include?(name)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.instantiate_from_raw_data(data)
|
55
|
+
data.delete_if { |hash| hash["name"] == "longdesc" } # Another way to specify comment[0]
|
56
|
+
data.delete_if { |hash| hash["name"].include?(".") } # Remove things like longdescs.count
|
57
|
+
data.collect do |field_hash|
|
58
|
+
new(field_hash)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def self.field_alias(value)
|
65
|
+
FIELD_ALIASES[value] || value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ActiveBugzilla
|
2
|
+
class Flag < Base
|
3
|
+
attr_reader :active, :bug_id, :created_on, :id, :name, :setter, :status, :type_id, :updated_on
|
4
|
+
alias_method :active?, :active
|
5
|
+
|
6
|
+
def initialize(attributes)
|
7
|
+
@id = attributes['id']
|
8
|
+
@bug_id = attributes['bug_id']
|
9
|
+
@type_id = attributes['type_id']
|
10
|
+
@created_on = normalize_timestamp(attributes['creation_date'])
|
11
|
+
@updated_on = normalize_timestamp(attributes['modification_date'])
|
12
|
+
@status = attributes['status']
|
13
|
+
@name = attributes['name']
|
14
|
+
@setter = attributes['setter']
|
15
|
+
@active = attributes['is_active']
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.instantiate_from_raw_data(data, bug_id = nil)
|
19
|
+
data.collect { |hash| new(hash.merge('bug_id' => bug_id)) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'xmlrpc/client'
|
2
|
+
|
3
|
+
module ActiveBugzilla
|
4
|
+
class Service
|
5
|
+
CLONE_FIELDS = [
|
6
|
+
:assigned_to,
|
7
|
+
:cc,
|
8
|
+
:cf_devel_whiteboard,
|
9
|
+
:cf_internal_whiteboard,
|
10
|
+
:comments,
|
11
|
+
:component,
|
12
|
+
:description,
|
13
|
+
:groups,
|
14
|
+
:keywords,
|
15
|
+
:op_sys,
|
16
|
+
:platform,
|
17
|
+
:priority,
|
18
|
+
:product,
|
19
|
+
:qa_contact,
|
20
|
+
:severity,
|
21
|
+
:summary,
|
22
|
+
:target_release,
|
23
|
+
:url,
|
24
|
+
:version,
|
25
|
+
:whiteboard
|
26
|
+
]
|
27
|
+
|
28
|
+
attr_accessor :bugzilla_uri, :username, :password, :last_command
|
29
|
+
attr_reader :bugzilla_request_uri, :bugzilla_request_hostname
|
30
|
+
|
31
|
+
def self.timeout=(value)
|
32
|
+
@@timeout = value
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.timeout
|
36
|
+
defined?(@@timeout) && @@timeout
|
37
|
+
end
|
38
|
+
|
39
|
+
def timeout
|
40
|
+
self.class.timeout
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.product=(value)
|
44
|
+
@@product = value
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.product
|
48
|
+
defined?(@@product) && @@product
|
49
|
+
end
|
50
|
+
|
51
|
+
def product
|
52
|
+
self.class.product
|
53
|
+
end
|
54
|
+
|
55
|
+
def bugzilla_uri=(value)
|
56
|
+
@bugzilla_request_uri = URI.join(value, "xmlrpc.cgi").to_s
|
57
|
+
@bugzilla_request_hostname = URI(value).hostname
|
58
|
+
@bugzilla_uri = value
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(bugzilla_uri, username, password)
|
62
|
+
raise ArgumentError, "username and password must be set" if username.nil? || password.nil?
|
63
|
+
|
64
|
+
self.bugzilla_uri = bugzilla_uri
|
65
|
+
self.username = username
|
66
|
+
self.password = password
|
67
|
+
end
|
68
|
+
|
69
|
+
def inspect
|
70
|
+
super.gsub(/@password=\".+?\", /, "")
|
71
|
+
end
|
72
|
+
|
73
|
+
# http://www.bugzilla.org/docs/4.4/en/html/api/Bugzilla/WebService/Bug.html#comments
|
74
|
+
def comments(params = {})
|
75
|
+
execute('comments', params)
|
76
|
+
end
|
77
|
+
|
78
|
+
# http://www.bugzilla.org/docs/4.4/en/html/api/Bugzilla/WebService/Bug.html#add_comment
|
79
|
+
def add_comment(bug_id, comment, params = {})
|
80
|
+
params[:id] = bug_id
|
81
|
+
params[:comment] = comment
|
82
|
+
execute('add_comment', params)["id"]
|
83
|
+
end
|
84
|
+
|
85
|
+
# http://www.bugzilla.org/docs/4.4/en/html/api/Bugzilla/WebService/Bug.html#fields
|
86
|
+
def fields(params = {})
|
87
|
+
execute('fields', params)['fields']
|
88
|
+
end
|
89
|
+
|
90
|
+
# http://www.bugzilla.org/docs/4.4/en/html/api/Bugzilla/WebService/Bug.html#get
|
91
|
+
# XMLRPC Bug Query of an existing bug
|
92
|
+
#
|
93
|
+
# Example:
|
94
|
+
# # Perform an xmlrpc query for a single bug.
|
95
|
+
# bz.get(948970)
|
96
|
+
#
|
97
|
+
# @param bug_id [Array, String, Fixnum] One or more bug ids to process.
|
98
|
+
# @return [Array] Array of matching bug hashes.
|
99
|
+
def get(bug_ids, params = {})
|
100
|
+
bug_ids = Array(bug_ids)
|
101
|
+
raise ArgumentError, "bug_ids must be all Numeric" unless bug_ids.all? { |id| id.to_s =~ /^\d+$/ }
|
102
|
+
|
103
|
+
params[:ids] = bug_ids
|
104
|
+
|
105
|
+
results = execute('get', params)['bugs']
|
106
|
+
return [] if results.nil?
|
107
|
+
results
|
108
|
+
end
|
109
|
+
|
110
|
+
# http://www.bugzilla.org/docs/4.4/en/html/api/Bugzilla/WebService/Bug.html#search
|
111
|
+
def search(params = {})
|
112
|
+
params[:creation_time] &&= to_xmlrpc_timestamp(params[:creation_time])
|
113
|
+
params[:last_change_time] &&= to_xmlrpc_timestamp(params[:last_change_time])
|
114
|
+
params[:product] ||= product if product
|
115
|
+
|
116
|
+
results = execute('search', params)['bugs']
|
117
|
+
return [] if results.nil?
|
118
|
+
results
|
119
|
+
end
|
120
|
+
|
121
|
+
# http://www.bugzilla.org/docs/4.4/en/html/api/Bugzilla/WebService/Bug.html#update
|
122
|
+
def update(ids, params = {})
|
123
|
+
params[:ids] = Array(ids)
|
124
|
+
execute('update', params)['bugs']
|
125
|
+
end
|
126
|
+
|
127
|
+
# http://www.bugzilla.org/docs/4.4/en/html/api/Bugzilla/WebService/Bug.html#create
|
128
|
+
def create(params)
|
129
|
+
execute('create', params)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Clone of an existing bug
|
133
|
+
#
|
134
|
+
# Example:
|
135
|
+
# # Perform a clone of an existing bug, and return the new bug ID.
|
136
|
+
# bz.clone(948970)
|
137
|
+
#
|
138
|
+
# @param bug_id [String, Fixnum] A single bug id to process.
|
139
|
+
# @param overrides [Hash] The properties to change from the source bug. Some properties include
|
140
|
+
# * <tt>:target_release</tt> - The target release for the new cloned bug.
|
141
|
+
# * <tt>:assigned_to</tt> - The person to assign the new cloned bug to.
|
142
|
+
# @return [Fixnum] The bug id to the new, cloned, bug.
|
143
|
+
def clone(bug_id, overrides = {})
|
144
|
+
raise ArgumentError, "bug_id must be numeric" unless bug_id.to_s =~ /^\d+$/
|
145
|
+
|
146
|
+
existing_bz = get(bug_id, :include_fields => CLONE_FIELDS).first
|
147
|
+
|
148
|
+
clone_description, clone_comment_is_private = assemble_clone_description(existing_bz)
|
149
|
+
|
150
|
+
params = {}
|
151
|
+
CLONE_FIELDS.each do |field|
|
152
|
+
next if field == :comments
|
153
|
+
params[field] = existing_bz[field.to_s]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Apply overrides
|
157
|
+
overrides.each do |param, value|
|
158
|
+
params[param] = value
|
159
|
+
end
|
160
|
+
|
161
|
+
# Apply base clone fields
|
162
|
+
params[:cf_clone_of] = bug_id
|
163
|
+
params[:description] = clone_description
|
164
|
+
params[:comment_is_private] = clone_comment_is_private
|
165
|
+
|
166
|
+
create(params)[:id.to_s]
|
167
|
+
end
|
168
|
+
|
169
|
+
# Bypass python-bugzilla and use the xmlrpc API directly.
|
170
|
+
def execute(action, params)
|
171
|
+
cmd = "Bug.#{action}"
|
172
|
+
|
173
|
+
params[:Bugzilla_login] ||= username
|
174
|
+
params[:Bugzilla_password] ||= password
|
175
|
+
|
176
|
+
self.last_command = command_string(cmd, params)
|
177
|
+
xmlrpc_client.call(cmd, params)
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
DEFAULT_CGI_PATH = '/xmlrpc.cgi'
|
183
|
+
DEFAULT_PORT = 443
|
184
|
+
DEFAULT_PROXY_HOST = nil
|
185
|
+
DEFAULT_PROXY_PORT = nil
|
186
|
+
DEFAULT_USE_SSL = true
|
187
|
+
DEFAULT_TIMEOUT = 120
|
188
|
+
|
189
|
+
def xmlrpc_client
|
190
|
+
@xmlrpc_client ||= ::XMLRPC::Client.new(
|
191
|
+
bugzilla_request_hostname,
|
192
|
+
DEFAULT_CGI_PATH,
|
193
|
+
DEFAULT_PORT,
|
194
|
+
DEFAULT_PROXY_HOST,
|
195
|
+
DEFAULT_PROXY_PORT,
|
196
|
+
username,
|
197
|
+
password,
|
198
|
+
DEFAULT_USE_SSL,
|
199
|
+
timeout || DEFAULT_TIMEOUT)
|
200
|
+
end
|
201
|
+
|
202
|
+
def to_xmlrpc_timestamp(ts)
|
203
|
+
return ts if ts.kind_of?(XMLRPC::DateTime)
|
204
|
+
return ts unless ts.respond_to?(:to_time)
|
205
|
+
ts = ts.to_time
|
206
|
+
XMLRPC::DateTime.new(ts.year, ts.month, ts.day, ts.hour, ts.min, ts.sec)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Build a printable representation of the xmlrcp command executed.
|
210
|
+
def command_string(cmd, params)
|
211
|
+
clean_params = Hash[params]
|
212
|
+
clean_params[:Bugzilla_password] = "********"
|
213
|
+
"xmlrpc_client.call(#{cmd}, #{clean_params})"
|
214
|
+
end
|
215
|
+
|
216
|
+
def assemble_clone_description(existing_bz)
|
217
|
+
clone_description = " +++ This bug was initially created as a clone of Bug ##{existing_bz[:id]} +++ \n"
|
218
|
+
clone_description << existing_bz[:description.to_s]
|
219
|
+
|
220
|
+
clone_comment_is_private = false
|
221
|
+
existing_bz[:comments.to_s].each do |comment|
|
222
|
+
clone_description << "\n\n"
|
223
|
+
clone_description << "*" * 70
|
224
|
+
clone_description << "\nFollowing comment by %s on %s\n\n" %
|
225
|
+
[comment['author'], comment['creation_time'].to_time]
|
226
|
+
clone_description << "\n\n"
|
227
|
+
clone_description << comment['text']
|
228
|
+
clone_comment_is_private = true if comment['is_private']
|
229
|
+
end
|
230
|
+
|
231
|
+
[clone_description, clone_comment_is_private]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveBugzilla::Bug do
|
4
|
+
context "#new" do
|
5
|
+
before(:each) do
|
6
|
+
@service_mapping = {
|
7
|
+
# Bug => XMLRPC
|
8
|
+
:severity => :severity_xmlrpc,
|
9
|
+
:priority => :priority_xmlrpc,
|
10
|
+
}
|
11
|
+
@service = double('service')
|
12
|
+
ActiveBugzilla::Base.service = @service
|
13
|
+
described_class.stub(:generate_xmlrpc_map).and_return(@service_mapping)
|
14
|
+
described_class.stub(:xmlrpc_timestamps).and_return([])
|
15
|
+
@id = 123
|
16
|
+
@bug = described_class.new(:id => @id)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "attribute_names" do
|
20
|
+
raw_keys = @service_mapping.values
|
21
|
+
raw_data = {}
|
22
|
+
raw_keys.each { |k| raw_data[k.to_s] = 'foo' }
|
23
|
+
@bug.stub(:raw_data).and_return(raw_data)
|
24
|
+
expect(@bug.attribute_names).to eq(@service_mapping.keys.sort_by { |key| key.to_s })
|
25
|
+
end
|
26
|
+
|
27
|
+
it "severity" do
|
28
|
+
severity = 'foo'
|
29
|
+
raw_data = {'severity_xmlrpc' => severity}
|
30
|
+
@bug.stub(:raw_data).and_return(raw_data)
|
31
|
+
expect(@bug.severity).to eq(severity)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "comments" do
|
35
|
+
comments_hash = [{'id' => 1}]
|
36
|
+
raw_data = {'comments' => comments_hash}
|
37
|
+
@bug.stub(:raw_data).and_return(raw_data)
|
38
|
+
comments = @bug.comments
|
39
|
+
expect(comments).to be_kind_of(Array)
|
40
|
+
expect(comments.count).to eq(1)
|
41
|
+
expect(comments.first).to be_kind_of(ActiveBugzilla::Comment)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveBugzilla::Comment do
|
4
|
+
before(:each) do
|
5
|
+
@author = 'author@example.com'
|
6
|
+
@bug_id = 123
|
7
|
+
@count = 0
|
8
|
+
@id = 42
|
9
|
+
@text = "This is a comment"
|
10
|
+
@is_private = true
|
11
|
+
|
12
|
+
@bug_comment = described_class.new(
|
13
|
+
'author' => @author,
|
14
|
+
'bug_id' => @bug_id,
|
15
|
+
'count' => @count,
|
16
|
+
'id' => @id,
|
17
|
+
'text' => @text,
|
18
|
+
'is_private' => @is_private)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "#private?" do
|
22
|
+
expect(@bug_comment.private?).to eq(@is_private)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "#created_by" do
|
26
|
+
expect(@bug_comment.created_by).to eq(@author)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "#bug_id" do
|
30
|
+
expect(@bug_comment.bug_id).to eq(@bug_id)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "#id" do
|
34
|
+
expect(@bug_comment.id).to eq(@id)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "#text" do
|
38
|
+
expect(@bug_comment.text).to eq(@text)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ActiveBugzilla::Service do
|
4
|
+
let(:bz) { described_class.new("http://uri.to/bugzilla", "calvin", "hobbes") }
|
5
|
+
|
6
|
+
context "#new" do
|
7
|
+
it 'normal case' do
|
8
|
+
expect { bz }.to_not raise_error
|
9
|
+
end
|
10
|
+
|
11
|
+
it "when bugzilla_uri is invalid" do
|
12
|
+
expect { described_class.new("lalala", "", "") }.to raise_error(URI::BadURIError)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "when username and password are not set" do
|
16
|
+
expect { described_class.new("http://uri.to/bugzilla", nil, nil) }.to raise_error(ArgumentError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "#get" do
|
21
|
+
it "when no argument is specified" do
|
22
|
+
expect { bz.get }.to raise_error(ArgumentError)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "when an invalid argument is specified" do
|
26
|
+
expect { bz.get("not a Fixnum") }.to raise_error(ArgumentError)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "when the specified bug does not exist" do
|
30
|
+
output = {}
|
31
|
+
|
32
|
+
allow(::XMLRPC::Client).to receive(:new).and_return(double('xmlrpc_client', :call => output))
|
33
|
+
matches = bz.get(94897099)
|
34
|
+
expect(matches).to be_kind_of(Array)
|
35
|
+
expect(matches).to be_empty
|
36
|
+
end
|
37
|
+
|
38
|
+
it "when producing valid output" do
|
39
|
+
output = {
|
40
|
+
'bugs' => [
|
41
|
+
{
|
42
|
+
"priority" => "unspecified",
|
43
|
+
"keywords" => ["ZStream"],
|
44
|
+
"cc" => ["calvin@redhat.com", "hobbes@RedHat.com"],
|
45
|
+
},
|
46
|
+
]
|
47
|
+
}
|
48
|
+
|
49
|
+
allow(::XMLRPC::Client).to receive(:new).and_return(double('xmlrpc_client', :call => output))
|
50
|
+
existing_bz = bz.get("948972").first
|
51
|
+
|
52
|
+
expect(bz.last_command).to include("Bug.get")
|
53
|
+
|
54
|
+
expect(existing_bz["priority"]).to eq("unspecified")
|
55
|
+
expect(existing_bz["keywords"]).to eq(["ZStream"])
|
56
|
+
expect(existing_bz["cc"]).to eq(["calvin@redhat.com", "hobbes@RedHat.com"])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "#clone" do
|
61
|
+
it "when no argument is specified" do
|
62
|
+
expect { bz.clone }.to raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "when an invalid argument is specified" do
|
66
|
+
expect { bz.clone("not a Fixnum") }.to raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "when the specified bug to clone does not exist" do
|
70
|
+
output = {}
|
71
|
+
|
72
|
+
allow(::XMLRPC::Client).to receive(:new).and_return(double('xmlrpc_client', :call => output))
|
73
|
+
expect { bz.clone(94897099) }.to raise_error
|
74
|
+
end
|
75
|
+
|
76
|
+
it "when producing valid output" do
|
77
|
+
output = {"id" => 948992}
|
78
|
+
existing_bz = {
|
79
|
+
"description" => "Description of problem:\n\nIt's Broken",
|
80
|
+
"priority" => "unspecified",
|
81
|
+
"assigned_to" => "calvin@redhat.com",
|
82
|
+
"target_release" => ["---"],
|
83
|
+
"keywords" => ["ZStream"],
|
84
|
+
"cc" => ["calvin@redhat.com", "hobbes@RedHat.com"],
|
85
|
+
"comments" => [
|
86
|
+
{
|
87
|
+
"is_private" => false,
|
88
|
+
"count" => 0,
|
89
|
+
"time" => XMLRPC::DateTime.new(1969, 7, 20, 16, 18, 30),
|
90
|
+
"bug_id" => 948970,
|
91
|
+
"author" => "Calvin@redhat.com",
|
92
|
+
"text" => "It's Broken and impossible to reproduce",
|
93
|
+
"creation_time" => XMLRPC::DateTime.new(1969, 7, 20, 16, 18, 30),
|
94
|
+
"id" => 5777871,
|
95
|
+
"creator_id" => 349490
|
96
|
+
},
|
97
|
+
{
|
98
|
+
"is_private" => false,
|
99
|
+
"count" => 1,
|
100
|
+
"time" => XMLRPC::DateTime.new(1970, 11, 10, 16, 18, 30),
|
101
|
+
"bug_id" => 948970,
|
102
|
+
"author" => "Hobbes@redhat.com",
|
103
|
+
"text" => "Fix Me Now!",
|
104
|
+
"creation_time" => XMLRPC::DateTime.new(1972, 2, 14, 0, 0, 0),
|
105
|
+
"id" => 5782170,
|
106
|
+
"creator_id" => 349490
|
107
|
+
},
|
108
|
+
]
|
109
|
+
}
|
110
|
+
|
111
|
+
described_class.any_instance.stub(:get).and_return([existing_bz])
|
112
|
+
allow(::XMLRPC::Client).to receive(:new).and_return(double('xmlrpc_create', :call => output))
|
113
|
+
new_bz_id = bz.clone("948972")
|
114
|
+
|
115
|
+
expect(bz.last_command).to include("Bug.create")
|
116
|
+
|
117
|
+
expect(new_bz_id).to eq(output["id"])
|
118
|
+
end
|
119
|
+
|
120
|
+
it "when providing override values" do
|
121
|
+
output = {"id" => 948992}
|
122
|
+
existing_bz = {
|
123
|
+
"description" => "Description of problem:\n\nIt's Broken",
|
124
|
+
"priority" => "unspecified",
|
125
|
+
"assigned_to" => "calvin@redhat.com",
|
126
|
+
"target_release" => ["---"],
|
127
|
+
"keywords" => ["ZStream"],
|
128
|
+
"cc" => ["calvin@redhat.com", "hobbes@RedHat.com"],
|
129
|
+
"comments" => [
|
130
|
+
{
|
131
|
+
"is_private" => false,
|
132
|
+
"count" => 0,
|
133
|
+
"time" => XMLRPC::DateTime.new(1969, 7, 20, 16, 18, 30),
|
134
|
+
"bug_id" => 948970,
|
135
|
+
"author" => "Buzz.Aldrin@redhat.com",
|
136
|
+
"text" => "It's Broken and impossible to reproduce",
|
137
|
+
"creation_time" => XMLRPC::DateTime.new(1969, 7, 20, 16, 18, 30),
|
138
|
+
"id" => 5777871,
|
139
|
+
"creator_id" => 349490
|
140
|
+
},
|
141
|
+
{
|
142
|
+
"is_private" => false,
|
143
|
+
"count" => 1,
|
144
|
+
"time" => XMLRPC::DateTime.new(1970, 11, 10, 16, 18, 30),
|
145
|
+
"bug_id" => 948970,
|
146
|
+
"author" => "Neil.Armstrong@redhat.com",
|
147
|
+
"text" => "Fix Me Now!",
|
148
|
+
"creation_time" => XMLRPC::DateTime.new(1972, 2, 14, 0, 0, 0),
|
149
|
+
"id" => 5782170,
|
150
|
+
"creator_id" => 349490
|
151
|
+
},
|
152
|
+
]
|
153
|
+
}
|
154
|
+
|
155
|
+
described_class.any_instance.stub(:get).and_return([existing_bz])
|
156
|
+
allow(::XMLRPC::Client).to receive(:new).and_return(double('xmlrpc_create', :call => output))
|
157
|
+
new_bz_id = bz.clone("948972", "assigned_to" => "Ham@NASA.gov", "target_release" => ["2.2.0"])
|
158
|
+
|
159
|
+
expect(bz.last_command).to include("Bug.create")
|
160
|
+
|
161
|
+
expect(new_bz_id).to eq(output["id"])
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
|
11
|
+
# Run specs in random order to surface order dependencies. If you find an
|
12
|
+
# order dependency and want to debug it, you can fix the order by providing
|
13
|
+
# the seed, which is printed after each run.
|
14
|
+
# --seed 1234
|
15
|
+
config.order = 'random'
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
require 'coveralls'
|
20
|
+
Coveralls.wear!
|
21
|
+
rescue LoadError
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'active_bugzilla'
|
metadata
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_bugzilla
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joe VLcek
|
9
|
+
- Jason Frey
|
10
|
+
- Oleg Barenboim
|
11
|
+
- Alberto Bellotti
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
date: 2014-03-31 00:00:00.000000000 Z
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: bundler
|
19
|
+
requirement: !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ~>
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '1.3'
|
25
|
+
type: :development
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.3'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
type: :development
|
42
|
+
prerelease: false
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rspec
|
51
|
+
requirement: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: coveralls
|
67
|
+
requirement: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ! '>='
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
type: :development
|
74
|
+
prerelease: false
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: activemodel
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activesupport
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
none: false
|
101
|
+
requirements:
|
102
|
+
- - ! '>='
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ! '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: dirty_hashy
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
117
|
+
requirements:
|
118
|
+
- - ! '>='
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
type: :runtime
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
124
|
+
none: false
|
125
|
+
requirements:
|
126
|
+
- - ! '>='
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '0'
|
129
|
+
description: ActiveBugzilla is an ActiveRecord like interface to the Bugzilla API.
|
130
|
+
email:
|
131
|
+
- jvlcek@redhat.com
|
132
|
+
- jfrey@redhat.com
|
133
|
+
- chessbyte@gmail.com
|
134
|
+
- abellott@redhat.com
|
135
|
+
executables: []
|
136
|
+
extensions: []
|
137
|
+
extra_rdoc_files: []
|
138
|
+
files:
|
139
|
+
- lib/active_bugzilla.rb
|
140
|
+
- lib/active_bugzilla/base.rb
|
141
|
+
- lib/active_bugzilla/bug.rb
|
142
|
+
- lib/active_bugzilla/bug/flags_management.rb
|
143
|
+
- lib/active_bugzilla/bug/service_management.rb
|
144
|
+
- lib/active_bugzilla/comment.rb
|
145
|
+
- lib/active_bugzilla/field.rb
|
146
|
+
- lib/active_bugzilla/flag.rb
|
147
|
+
- lib/active_bugzilla/service.rb
|
148
|
+
- lib/active_bugzilla/version.rb
|
149
|
+
- README.md
|
150
|
+
- LICENSE.txt
|
151
|
+
- spec/active_bugzilla/bug_spec.rb
|
152
|
+
- spec/active_bugzilla/comment_spec.rb
|
153
|
+
- spec/active_bugzilla/service_spec.rb
|
154
|
+
- spec/active_bugzilla_spec.rb
|
155
|
+
- spec/spec_helper.rb
|
156
|
+
homepage: http://github.com/ManageIQ/active_bugzilla
|
157
|
+
licenses:
|
158
|
+
- MIT
|
159
|
+
post_install_message:
|
160
|
+
rdoc_options: []
|
161
|
+
require_paths:
|
162
|
+
- lib
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ! '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
none: false
|
171
|
+
requirements:
|
172
|
+
- - ! '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
requirements: []
|
176
|
+
rubyforge_project:
|
177
|
+
rubygems_version: 1.8.23
|
178
|
+
signing_key:
|
179
|
+
specification_version: 3
|
180
|
+
summary: ActiveBugzilla is an ActiveRecord like interface to the Bugzilla API.
|
181
|
+
test_files:
|
182
|
+
- spec/active_bugzilla/bug_spec.rb
|
183
|
+
- spec/active_bugzilla/comment_spec.rb
|
184
|
+
- spec/active_bugzilla/service_spec.rb
|
185
|
+
- spec/active_bugzilla_spec.rb
|
186
|
+
- spec/spec_helper.rb
|
187
|
+
has_rdoc:
|