active_bugzilla 1.0.0
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.
- 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
|
+
[](http://badge.fury.io/rb/active_bugzilla)
|
4
|
+
[](https://travis-ci.org/ManageIQ/active_bugzilla)
|
5
|
+
[](https://codeclimate.com/github/ManageIQ/active_bugzilla)
|
6
|
+
[](https://coveralls.io/r/ManageIQ/active_bugzilla)
|
7
|
+
[](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:
|