├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── .travis.yml ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── bicho.gemspec ├── bin └── bicho ├── lib ├── bicho.rb └── bicho │ ├── attachment.rb │ ├── bug.rb │ ├── cli │ ├── command.rb │ └── commands │ │ ├── attachments.rb │ │ ├── history.rb │ │ ├── reopen.rb │ │ ├── search.rb │ │ ├── show.rb │ │ └── version.rb │ ├── client.rb │ ├── common_client.rb │ ├── export.rb │ ├── ext │ └── logger_colors.rb │ ├── history.rb │ ├── logging.rb │ ├── plugins │ ├── aliases.rb │ ├── novell.rb │ └── user.rb │ ├── query.rb │ ├── reports.rb │ └── version.rb └── test ├── fixtures ├── 777777.json ├── bugzilla_gnome_org_search_prometheus_export.txt └── vcr │ ├── bugzilla_gnome_org_312619.yml │ ├── bugzilla_gnome_org_645150_history.yml │ ├── bugzilla_gnome_org_679745_attachments.yml │ ├── bugzilla_gnome_org_777777.yml │ ├── bugzilla_gnome_org_777777_export.yml │ ├── bugzilla_gnome_org_search_prometheus_export.yml │ ├── bugzilla_gnome_org_search_vala_resolved_basic_types.yml │ ├── bugzilla_gnome_org_version.yml │ └── bugzilla_opensuse_org_version.yml ├── helper.rb ├── test_attachments.rb ├── test_export.rb ├── test_history.rb ├── test_novell_plugin.rb ├── test_query.rb ├── test_reports.rb ├── test_user_plugin.rb └── test_version.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | .yardoc 6 | *.rbc 7 | *~ 8 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | Style/AsciiComments: 4 | AllowedChars: ['ä', 'ü', 'ö'] 5 | 6 | Metrics/ClassLength: 7 | Enabled: false 8 | 9 | AllCops: 10 | TargetRubyVersion: 2.3 11 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2018-10-23 14:23:33 +0200 using RuboCop version 0.59.2. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # Cop supports --auto-correct. 11 | Layout/ClosingHeredocIndentation: 12 | Exclude: 13 | - 'bin/bicho' 14 | 15 | # Offense count: 9 16 | # Cop supports --auto-correct. 17 | Layout/EmptyLineAfterGuardClause: 18 | Exclude: 19 | - 'lib/bicho/bug.rb' 20 | - 'lib/bicho/cli/commands/search.rb' 21 | - 'lib/bicho/client.rb' 22 | - 'lib/bicho/history.rb' 23 | - 'lib/bicho/plugins/novell.rb' 24 | - 'lib/bicho/query.rb' 25 | - 'test/test_attachments.rb' 26 | 27 | # Offense count: 2 28 | # Cop supports --auto-correct. 29 | # Configuration parameters: EnforcedStyle. 30 | # SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent 31 | Layout/IndentHeredoc: 32 | Exclude: 33 | - 'test/test_novell_plugin.rb' 34 | - 'test/test_user_plugin.rb' 35 | 36 | # Offense count: 1 37 | # Cop supports --auto-correct. 38 | Layout/RescueEnsureAlignment: 39 | Exclude: 40 | - 'lib/bicho/ext/logger_colors.rb' 41 | 42 | # Offense count: 1 43 | Lint/UriEscapeUnescape: 44 | Exclude: 45 | - 'lib/bicho/client.rb' 46 | 47 | # Offense count: 14 48 | Metrics/AbcSize: 49 | Max: 43 50 | 51 | # Offense count: 1 52 | # Configuration parameters: CountComments. 53 | Metrics/ClassLength: 54 | Max: 200 55 | 56 | # Offense count: 1 57 | Metrics/CyclomaticComplexity: 58 | Max: 12 59 | 60 | # Offense count: 15 61 | # Configuration parameters: CountComments, ExcludedMethods. 62 | Metrics/MethodLength: 63 | Max: 38 64 | 65 | # Offense count: 1 66 | Metrics/PerceivedComplexity: 67 | Max: 12 68 | 69 | # Offense count: 3 70 | # Configuration parameters: Blacklist. 71 | # Blacklist: (?-mix:(^|\s)(EO[A-Z]{1}|END)(\s|$)) 72 | Naming/HeredocDelimiterNaming: 73 | Exclude: 74 | - 'bin/bicho' 75 | - 'test/test_novell_plugin.rb' 76 | - 'test/test_user_plugin.rb' 77 | 78 | # Offense count: 1 79 | # Configuration parameters: . 80 | # SupportedStyles: snake_case, camelCase 81 | Naming/MethodName: 82 | EnforcedStyle: snake_case 83 | 84 | # Offense count: 1 85 | # Cop supports --auto-correct. 86 | # Configuration parameters: AutoCorrect. 87 | Security/JSONLoad: 88 | Exclude: 89 | - 'test/test_export.rb' 90 | 91 | # Offense count: 7 92 | # Cop supports --auto-correct. 93 | # Configuration parameters: AutoCorrect, EnforcedStyle. 94 | # SupportedStyles: nested, compact 95 | Style/ClassAndModuleChildren: 96 | Exclude: 97 | - 'lib/bicho/cli/commands/attachments.rb' 98 | - 'lib/bicho/cli/commands/history.rb' 99 | - 'lib/bicho/cli/commands/search.rb' 100 | - 'lib/bicho/cli/commands/show.rb' 101 | - 'lib/bicho/cli/commands/version.rb' 102 | - 'lib/bicho/client.rb' 103 | 104 | # Offense count: 3 105 | # Configuration parameters: . 106 | # SupportedStyles: annotated, template, unannotated 107 | Style/FormatStringToken: 108 | EnforcedStyle: template 109 | 110 | # Offense count: 3 111 | # Configuration parameters: MinBodyLength. 112 | Style/GuardClause: 113 | Exclude: 114 | - 'lib/bicho/client.rb' 115 | 116 | # Offense count: 5 117 | # Cop supports --auto-correct. 118 | Style/IfUnlessModifier: 119 | Exclude: 120 | - 'bin/bicho' 121 | - 'lib/bicho/client.rb' 122 | 123 | # Offense count: 6 124 | # Cop supports --auto-correct. 125 | # Configuration parameters: Strict. 126 | Style/NumericLiterals: 127 | MinDigits: 7 128 | 129 | # Offense count: 1 130 | # Cop supports --auto-correct. 131 | Style/OrAssignment: 132 | Exclude: 133 | - 'lib/bicho/history.rb' 134 | 135 | # Offense count: 5 136 | # Cop supports --auto-correct. 137 | # Configuration parameters: PreferredDelimiters. 138 | Style/PercentLiteralDelimiters: 139 | Exclude: 140 | - 'lib/bicho/ext/logger_colors.rb' 141 | - 'test/test_novell_plugin.rb' 142 | - 'test/test_query.rb' 143 | 144 | # Offense count: 1 145 | # Cop supports --auto-correct. 146 | # Configuration parameters: EnforcedStyle. 147 | # SupportedStyles: implicit, explicit 148 | Style/RescueStandardError: 149 | Exclude: 150 | - 'bin/bicho' 151 | 152 | # Offense count: 2 153 | # Cop supports --auto-correct. 154 | # Configuration parameters: MinSize. 155 | # SupportedStyles: percent, brackets 156 | Style/SymbolArray: 157 | EnforcedStyle: brackets 158 | 159 | # Offense count: 66 160 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 161 | # URISchemes: http, https 162 | Metrics/LineLength: 163 | Max: 356 164 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.6 4 | - 2.5 5 | - 2.4 6 | - 2.3 7 | script: 8 | - bundle exec rake test 9 | - bundle exec rubocop 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'http://rubygems.org' 4 | 5 | # Specify your gem's dependencies in bicho.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 SUSE LINUX Products GmbH 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bicho 2 | 3 | * http://github.com/dmacvicar/bicho 4 | 5 | ![stable](https://img.shields.io/badge/stability-stable-green.svg) 6 | ![maintained](https://img.shields.io/maintenance/yes/2019.svg) 7 | [![Build Status](https://travis-ci.org/dmacvicar/bicho.svg?branch=master)](https://travis-ci.org/dmacvicar/bicho) 8 | 9 | ## Introduction 10 | 11 | Library to access bugzilla and command line tool. 12 | 13 | Its main goal is to be clean and provide a command line tool 14 | that exposes its features. 15 | 16 | ## Features 17 | 18 | Main use case is report generation, therefore only the following 19 | features are implemented right now: 20 | 21 | * get bugs 22 | * search bugs (create queries) 23 | 24 | Plugins can be written to deal with specific bugzilla installations. 25 | 26 | Additionally, some utilities are provided, like exporting the total number of bugs of a query to the [Prometheus push gateway](https://prometheus.io/docs/practices/pushing/) format. 27 | 28 | ## Example (API) 29 | 30 | ### Client API 31 | 32 | ```ruby 33 | require 'bicho' 34 | 35 | server = Bicho::Client.new('http://bugzilla.gnome.org') 36 | server.get_bugs(127043).each do |bug| 37 | puts bug.summary 38 | puts bug.url 39 | 40 | puts bug.history 41 | end 42 | ``` 43 | 44 | You can give more than one bug or a named query, or both: 45 | 46 | ```ruby 47 | server.get_bugs(127043, 432423) => [....] 48 | server.get_bugs("Named list") => [....] 49 | server.get_bugs("Named list", 4423443) => [....] 50 | ``` 51 | 52 | ### ActiveRecord-like API 53 | 54 | To use the ActiveRecord like interface over the +Bug+ class, you need first 55 | to set the Bicho common client: 56 | 57 | ```ruby 58 | require 'bicho' 59 | 60 | Bicho.client = Bicho::Client.new('https://bugzilla.gnome.org') 61 | 62 | Bicho::Bug.where(product: 'vala', status: 'resolved').each do |bug| 63 | # .. do something with bug 64 | end 65 | ``` 66 | 67 | Or alternatively: 68 | 69 | ```ruby 70 | Bicho::Bug.where.product('vala').status('resolved').each do |bug| 71 | # .. do something with bug 72 | end 73 | ``` 74 | 75 | ## Example (CLI) 76 | 77 | ```console 78 | bicho -b http://bugzilla.gnome.org show 127043 79 | 80 | bicho -b gnome history 127043 81 | 82 | bicho -b gnome search --summary "crash" 83 | 84 | bicho -b gnome search --help 85 | ``` 86 | 87 | ## Authentication 88 | 89 | For SUSE/Novell Bugzilla, a plugin loads the credentials from '~/.oscrc'. 90 | 91 | Otherwise, use the 'username:password@' part of the API URL. 92 | 93 | ## Customizing Bicho: the user.rb plugin. 94 | 95 | Plugins are included that provide shortcuts for the most common bugzilla sites. 96 | 97 | There is a "user" plugin that does some of these shortcuts from a configuration file. 98 | 99 | The settings are read from '.config/bicho/config.yml'. There you can specify the default 100 | bugzilla site to use when none is specified and custom aliases. 101 | 102 | ```yml 103 | aliases: 104 | mysite: http://bugzilla.site.com 105 | default: mysite 106 | ``` 107 | 108 | ## Extending Bicho 109 | 110 | ### Plugins 111 | 112 | Plugins are classes in the module Bicho::Plugins. They can implement hooks that are 113 | called at different points of execution. 114 | 115 | * `default_site_url_hook` 116 | 117 | If no site url is provided the last one provided by a plugin will be used. 118 | 119 | * `transform_site_url_hook` 120 | 121 | This hook is called to modify the main site url (eg: http://bugzilla.suse.com). 122 | Use it when a plugin wants to provide an alternative url to a well-known bugzilla or 123 | a shortcut (eg: bnc) that will be expanded into a site url. 124 | Plugin order is not defined so make sure your plugin focuses in one type of shortcut 125 | as another plugin can also change your returned value in their hooks. 126 | 127 | * `transform_api_url_hook` 128 | 129 | The API url is derived from the site url, however some bugzilla installations may have 130 | different servers or endpoints. 131 | 132 | * `transform_xmlrpc_client_hook` 133 | 134 | This hook allows to modify the `XMLRPC::Client` object. 135 | 136 | ### Commands 137 | 138 | See the +Command+ class to implement more commands. 139 | 140 | ## Known issues 141 | 142 | * For now bugs respond to the bugs attributtes described in 143 | http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bug.html, I intend to make those real attributes. 144 | * There is no check if an API is supported on the server side 145 | 146 | ## Roadmap 147 | 148 | * Define the plugin hooks, right now there is one :-) 149 | * Shortcuts for the bugzilla URL (bicho -b bko search ..), a plugin? 150 | 151 | ## Authors 152 | 153 | * Duncan Mac-Vicar P. 154 | 155 | ## License 156 | 157 | Copyright (c) 2011-2015 SUSE LLC 158 | 159 | Bicho is licensed under the MIT license. See MIT-LICENSE for details. 160 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.push(File.join(File.dirname(__FILE__), 'lib')) 4 | require 'bundler/gem_tasks' 5 | require 'bicho/version' 6 | require 'rake/testtask' 7 | 8 | extra_docs = ['README*', 'TODO*', 'CHANGELOG*'] 9 | 10 | task default: [:test] 11 | Rake::TestTask.new do |t| 12 | t.pattern = 'test/test_*.rb' 13 | t.verbose = true if ENV['DEBUG'] 14 | end 15 | 16 | begin 17 | require 'yard' 18 | YARD::Rake::YardocTask.new(:doc) do |t| 19 | t.files = ['lib/**/*.rb', *extra_docs] 20 | t.options = ['--no-private'] 21 | end 22 | rescue LoadError 23 | STDERR.puts 'Install yard if you want prettier docs' 24 | require 'rdoc/task' 25 | Rake::RDocTask.new(:doc) do |rdoc| 26 | rdoc.rdoc_dir = 'doc' 27 | rdoc.title = "bicho #{Bicho::VERSION}" 28 | extra_docs.each { |ex| rdoc.rdoc_files.include ex } 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /bicho.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.push File.expand_path('lib', __dir__) 4 | require 'bicho/version' 5 | 6 | # rubocop:disable Metrics/BlockLength 7 | Gem::Specification.new do |s| 8 | s.name = 'bicho' 9 | s.version = Bicho::VERSION 10 | s.authors = ['Duncan Mac-Vicar P.'] 11 | s.email = ['dmacvicar@suse.de'] 12 | s.homepage = 'http://github.com/dmacvicar/bicho' 13 | s.summary = 'Library to access bugzilla' 14 | s.description = 'Library to access bugzilla' 15 | s.licenses = ['MIT'] 16 | 17 | s.add_dependency('highline', ['~> 2.0.0']) 18 | s.add_dependency('inifile', ['~> 3.0.0']) 19 | s.add_dependency('nokogiri', ['~> 1.10.4']) 20 | s.add_dependency('optimist', ['~> 3.0.0']) 21 | s.add_dependency('xmlrpc', ['~> 0.3.0']) 22 | 23 | s.add_development_dependency('minitest') 24 | s.add_development_dependency('minitest-reporters') 25 | s.add_development_dependency('rake') 26 | s.add_development_dependency('rubocop', '~> 0.59.0') 27 | s.add_development_dependency('solargraph') 28 | s.add_development_dependency('vcr') 29 | s.add_development_dependency('webmock') 30 | 31 | s.rubyforge_project = 'bicho' 32 | 33 | s.files = `git ls-files`.split("\n") 34 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 35 | s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } 36 | s.require_paths = ['lib'] 37 | s.required_ruby_version = '>= 2.3' 38 | end 39 | # rubocop:enable Metrics/BlockLength 40 | -------------------------------------------------------------------------------- /bin/bicho: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | #-- 5 | # Copyright (c) 2011 SUSE LINUX Products GmbH 6 | # 7 | # Author: Duncan Mac-Vicar P. 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining 10 | # a copy of this software and associated documentation files (the 11 | # "Software"), to deal in the Software without restriction, including 12 | # without limitation the rights to use, copy, modify, merge, publish, 13 | # distribute, sublicense, and/or sell copies of the Software, and to 14 | # permit persons to whom the Software is furnished to do so, subject to 15 | # the following conditions: 16 | # 17 | # The above copyright notice and this permission notice shall be 18 | # included in all copies or substantial portions of the Software. 19 | # 20 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | #++ 28 | $LOAD_PATH.push(File.join(File.dirname(__FILE__), '..', 'lib')) 29 | require 'bicho' 30 | require 'highline/import' 31 | 32 | # Set the console color scheme 33 | ft = HighLine::ColorScheme.new do |cs| 34 | cs[:headline] = [:bold] 35 | cs[:horizontal_line] = [:bold, :white] 36 | cs[:even_row] = [:green] 37 | cs[:odd_row] = [:magenta] 38 | cs[:error] = [:red] 39 | cs[:remove] = [:red] 40 | cs[:add] = [:green] 41 | cs[:changeset] = [:blue] 42 | end 43 | 44 | HighLine.color_scheme = ft 45 | # Our 'terminal' object 46 | t = HighLine.new 47 | 48 | # Scan all installed pluggable commands 49 | Dir.glob(File.join(File.dirname(__FILE__), '..', 'lib', 'bicho', 'cli', 'commands', '*.rb')).each do |cmd| 50 | load cmd 51 | end 52 | 53 | # Setup a logger and connect it with stupid libraries 54 | # that can't use a logger 55 | Bicho::Logging.logger = Logger.new(STDERR) 56 | # Don't show by default anything 57 | Bicho::Logging.logger.level = Logger::FATAL 58 | 59 | ret = 0 60 | begin 61 | # create subcommands automatically from the available 62 | # symbols in BzConsole module 63 | SUB_COMMANDS = Bicho::CLI::Commands.constants.sort.map(&:to_s).map(&:downcase) 64 | 65 | global_opts = Optimist.options do 66 | banner <<-EOS 67 | Usage: #{File.basename(__FILE__)} [global options] ... 68 | 69 | Bugzilla url can be given as https://bugs.kde.org or bko 70 | EOS 71 | opt :config, 'Read FILE as configuration file.', type: :io 72 | opt :debug, 'Show debug messages', default: false 73 | opt :bugzilla, 'Bugzilla URL or alias', type: :string 74 | stop_on SUB_COMMANDS 75 | end 76 | 77 | Bicho::Logging.logger.level = Logger::DEBUG if global_opts[:debug] 78 | 79 | # get the subcommand 80 | cmd = begin 81 | ARGV.shift 82 | rescue 83 | nil 84 | end 85 | 86 | if !cmd || !SUB_COMMANDS.include?(cmd) 87 | Optimist.die "available subcommands: #{SUB_COMMANDS.join(' ')}" 88 | end 89 | 90 | # Create an instance for the command 91 | mod = Bicho::CLI::Commands 92 | cmd_class = mod.const_get(cmd.capitalize) 93 | cmd_instance = cmd_class.new 94 | opts = cmd_instance.parse_options 95 | 96 | ret = cmd_instance.do(global_opts, opts, ARGV) 97 | rescue StandardError => e 98 | Bicho::Logging.logger.error e.message 99 | Bicho::Logging.logger.error e.backtrace 100 | t.say("#{t.color('ERROR: ', :error)} #{t.color(e.message, :bold)}") 101 | ret = 1 102 | end 103 | exit ret 104 | -------------------------------------------------------------------------------- /lib/bicho.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | require 'rubygems' 28 | require 'logger' 29 | require 'bicho/version' 30 | require 'bicho/logging' 31 | require 'bicho/client' 32 | require 'bicho/bug' 33 | require 'bicho/reports' 34 | require 'bicho/export' 35 | 36 | # All classes of the Bicho library belong to this module. 37 | module Bicho 38 | SEARCH_FIELDS = [ 39 | # name, type, description, multi 40 | [:alias, :strings, 'The unique alias for this bug', true], 41 | [:assigned_to, :strings, 'The login name of a user that a bug is assigned to.', true], 42 | [:component, :strings, 'The name of the Component', true], 43 | [:creation_time, :string, 'Searches for bugs that were created at this time or later.', false], 44 | [:id, :ints, 'The numeric id of the bug.', true], 45 | [:last_change_time, :string, 'Searches for bugs that were modified at this time or later.', false], 46 | [:limit, :ints, 'Limit the number of results returned to int records.', true], 47 | [:offset, :ints, 'Used in conjunction with the limit argument, offset defines the starting position for the search.', true], 48 | [:op_sys, :strings, "The 'Operating System' field of a bug.", true], 49 | [:platform, :strings, 'The Platform field of a bug.', true], 50 | [:priority, :strings, 'The Priority field on a bug.', true], 51 | [:product, :strings, 'The name of the Product that the bug is in.', true], 52 | [:reporter, :strings, 'The login name of the user who reported the bug.', true], 53 | [:resolution, :strings, 'The current resolution--only set if a bug is closed.', true], 54 | [:severity, :strings, 'The Severity field on a bug.', true], 55 | [:status, :strings, 'The current status of a bug', true], 56 | [:summary, :strings, 'Searches for substrings in the single-line Summary field on bugs.', true], 57 | [:target_milestone, :strings, 'The Target Milestone field of a bug.', true], 58 | [:qa_contact, :strings, "The login name of the bug's QA Contact.", true], 59 | [:url, :strings, "The 'URL' field of a bug.", true], 60 | [:version, :strings, 'The Version field of a bug.', true], 61 | [:votes, :ints, 'Searches for bugs with this many votes or greater', false], 62 | [:whiteboard, :strings, "Search the 'Status Whiteboard' field on bugs for a substring.", true] 63 | ].freeze 64 | end 65 | -------------------------------------------------------------------------------- /lib/bicho/attachment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2016 SUSE LLC 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | require 'stringio' 28 | 29 | module Bicho 30 | # Represents an attachment object for a given bug 31 | class Attachment 32 | attr_reader :props 33 | 34 | def initialize(client, xmlrpc_client, props) 35 | @client = client 36 | @xmlrpc_client = xmlrpc_client 37 | @props = props 38 | end 39 | 40 | # @return [Fixnum] attachment id 41 | def id 42 | props['id'].to_i 43 | end 44 | 45 | # @return [Fixnum] attachment bug id 46 | def bug_id 47 | props['bug_id'].to_i 48 | end 49 | 50 | # @return [String] attachment content type 51 | def content_type 52 | props['content_type'] 53 | end 54 | 55 | # @return [Fixnum] attachment size 56 | def size 57 | props['size'].to_i 58 | end 59 | 60 | # @return [String] attachment summary 61 | def summary 62 | props['summary'] 63 | end 64 | 65 | # @return [StringIO] attachmentdata 66 | # This will be loaded lazyly every time called 67 | def data 68 | ret = @xmlrpc_client.call('Bug.attachments', 69 | attachment_ids: [id], include_fields: ['data']) 70 | @client.handle_faults(ret) 71 | StringIO.new(ret['attachments'][id.to_s]['data']) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/bicho/bug.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bicho/query' 4 | 5 | module Bicho 6 | # A single bug inside a bugzilla instance. 7 | class Bug 8 | # ActiveRecord like interface 9 | # 10 | # @example Searching for bugs 11 | # Bug.where(:summary => "crash").each do |bug| 12 | # #...do something with bug 13 | # end 14 | # 15 | # Requires Bicho.client to be set 16 | # 17 | # @param [Hash] conds the conditions for the query. 18 | # alias 19 | # @option conds [String] The unique alias for this bug. 20 | # @option conds assigned_to [String] The login name of a user that a bug is assigned to. 21 | # @option conds component [String] The name of the Component that the bug is in. 22 | # @option conds creation_time [DateTime] Searches for bugs that were created at this time or later. May not be an array. 23 | # @option conds creator [String] The login name of the user who created the bug. 24 | # @option conds id [Integer] The numeric id of the bug. 25 | # @option conds last_change_time [DateTime] Searches for bugs that were modified at this time or later. May not be an array. 26 | # @option conds limit [Integer] Limit the number of results returned to int records. 27 | # @option conds offset [Integer] 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. 28 | # @option conds op_sys [String] The "Operating System" field of a bug. 29 | # @option conds platform [String] The Platform (sometimes called "Hardware") field of a bug. 30 | # @option conds priority [String] The Priority field on a bug. 31 | # @option conds product [String] The name of the Product that the bug is in. 32 | # @option conds creator [String] The login name of the user who reported the bug. 33 | # @option conds 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. 34 | # @option conds severity [String] The Severity field on a bug. 35 | # @option conds status [String] The current status of a bug (not including its resolution, if it has one, which is a separate field above). 36 | # @option conds 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. 37 | # @option conds 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). 38 | # @option conds 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). 39 | # @option conds url [String] The "URL" field of a bug. 40 | # @option conds version [String] The Version field of a bug. 41 | # @option conds whiteboard [String] Search the "Status Whiteboard" field on bugs for a substring. Works the same as the summary field described above, but searches the Status Whiteboard field. 42 | # 43 | # @return [Array] 44 | # 45 | # @see http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Bug.html#search bugzilla search API and allowed attributes 46 | # 47 | def self.where(conditions = {}) 48 | Query.new(conditions) 49 | end 50 | 51 | # Normally you will not use this constructor as 52 | # bugs will be constructed by a query result 53 | # 54 | # @param client [Bicho::Client] client where this bug gets its data 55 | # @param data [Hash] retrieved data for this bug 56 | def initialize(client, data) 57 | @client = client 58 | @data = data 59 | end 60 | 61 | def method_missing(method_name, *_args) 62 | return super unless @data.key?(method_name.to_s) 63 | self[method_name] 64 | end 65 | 66 | def respond_to_missing?(method_name, _include_private = false) 67 | @data.key?(method_name.to_s) || super 68 | end 69 | 70 | def id 71 | # we define id to not get the deprecated 72 | # warning of object_id 73 | @data['id'] 74 | end 75 | 76 | def to_s 77 | "##{id} - #{summary} (#{url})" 78 | end 79 | 80 | def [](name, subname = nil) 81 | value = @data[name.to_s] 82 | value = value[subname.to_s] if subname # for 'internals' properties 83 | if value.is_a?(XMLRPC::DateTime) 84 | value.to_time 85 | else 86 | value 87 | end 88 | end 89 | 90 | # reopen a closed bug 91 | # @param a String comment to add 92 | # @param @optional a Boolean indicator if the new comment should be private (defaults to 'false') 93 | # 94 | # @returns id of re-opened bug 95 | # 96 | def reopen!(comment, is_private = false) 97 | @client.update_bug(id, status: 'REOPENED', comment: { body: comment.to_s, is_private: is_private.to_b }) 98 | end 99 | 100 | # URL where the bug can be viewed 101 | # Example: http://bugs.foo.com/2345 102 | def url 103 | "#{@client.site_url}/#{id}" 104 | end 105 | 106 | # @return [History] history for this bug 107 | def history 108 | @client.get_history(id).first 109 | end 110 | 111 | # @return [Array] attachments for this bug 112 | def attachments 113 | @client.get_attachments(id) 114 | end 115 | 116 | # Add an attachment to the bug 117 | # For the params description, see the Client.add_attachment method. 118 | # 119 | # @return [ID] of the new attachment 120 | def add_attachment(summary, file, **kwargs) 121 | @client.add_attachment(summary, file, id, **kwargs).first 122 | end 123 | 124 | # @param format_string For Kernel#sprintf; named params supplied by the bug 125 | def format(format_string) 126 | sym_data = Hash[@data.to_a.map { |k, v| [k.to_sym, v] }] 127 | Kernel.format(format_string, sym_data) 128 | end 129 | 130 | # @return [Hash] 131 | def to_h 132 | Hash[@data.to_a.map { |k, _| [k.to_sym, self[k.to_sym]] }] 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /lib/bicho/cli/command.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | 28 | require 'optimist' 29 | require 'highline' 30 | 31 | module Bicho 32 | module CLI 33 | # Bicho allows to easily add commands to the 34 | # command line interface. 35 | # 36 | # In order to create a command, add a class under 37 | # Bicho::CLI::Commands. Then you need to: 38 | # * Add options, using a Optimist syntax 39 | # * Implement do(global_opts, options, args) 40 | # 41 | # You can use t.say to talk to the terminal 42 | # including all HighLine features. 43 | # 44 | # 45 | # class Bicho::CLI::Commands::Hello < ::Bicho::CLI::Command 46 | # options do 47 | # opt :monkey, "Use monkey mode", :default => true 48 | # opt :text, "Name", :type => :string 49 | # end 50 | # 51 | # def do(global_opts, opts, args) 52 | # t.say("Hello") 53 | # end 54 | # end 55 | # 56 | # 57 | class Command 58 | include ::Bicho::Logging 59 | 60 | class << self; attr_accessor :parser end 61 | 62 | attr_accessor :t 63 | 64 | def initialize 65 | @t = HighLine.new 66 | end 67 | 68 | # Gateway to Optimist 69 | def self.opt(*args) 70 | self.parser = Optimist::Parser.new unless parser 71 | parser.opt(*args) 72 | end 73 | 74 | # DSL method to describe a command's option 75 | def self.options 76 | yield 77 | end 78 | 79 | # Called by the cli to get the options 80 | # with current ARGV 81 | def parse_options 82 | self.class.parser = Optimist::Parser.new unless self.class.parser 83 | Optimist.with_standard_exception_handling(self.class.parser) do 84 | self.class.parser.parse ARGV 85 | end 86 | end 87 | 88 | def parser 89 | self.class.parser 90 | end 91 | 92 | def do(_opts, _args) 93 | raise "No implementation for #{self.class}" if self.class =~ /CommandTemplate/ 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/bicho/cli/commands/attachments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | 28 | require 'bicho/cli/command' 29 | require 'bicho/client' 30 | 31 | module Bicho::CLI::Commands 32 | # Command to display bug information. 33 | class Attachments < ::Bicho::CLI::Command 34 | private 35 | 36 | # check for supportconfigs and download 37 | def download(bug, supportconfig_only) 38 | bug.attachments.each do |attachment| 39 | filename = "bsc#{bug.id}-#{attachment.id}-#{attachment.props['file_name']}" 40 | if supportconfig_only 41 | next unless attachment.content_type == 'application/x-gzip' || 42 | attachment.content_type == 'application/x-bzip-compressed-tar' 43 | next unless attachment.summary =~ /supportconfig/i 44 | end 45 | t.say("Downloading to #{t.color(filename, :even_row)}") 46 | begin 47 | data = attachment.data 48 | File.open(filename, 'w') do |f| 49 | f.write data.read 50 | end 51 | rescue StandardError => e 52 | t.say("#{t.color('Error:', :error)} Download of #{filename} failed: #{e}") 53 | raise 54 | end 55 | end 56 | end 57 | 58 | public 59 | 60 | options do 61 | opt :download, 'Download attachments' 62 | opt :supportconfig, 'Only download supportconfig attachments' 63 | end 64 | 65 | def do(global_opts, opts, args) 66 | client = ::Bicho::Client.new(global_opts[:bugzilla]) 67 | client.get_bugs(*args).each do |bug| 68 | if opts[:download] 69 | download(bug, opts[:supportconfig]) 70 | else 71 | t.say("Bug #{t.color(bug.id.to_s, :headline)} has #{bug.attachments.size} attachments") 72 | end 73 | end 74 | 0 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/bicho/cli/commands/history.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | 28 | require 'bicho/cli/command' 29 | require 'bicho/client' 30 | require 'pp' 31 | 32 | module Bicho::CLI::Commands 33 | # command that shows the history colored as a 34 | # changelog 35 | class History < ::Bicho::CLI::Command 36 | def do(global_opts, _opts, args) 37 | client = ::Bicho::Client.new(global_opts[:bugzilla]) 38 | client.get_history(*args).each do |history| 39 | t.say("#{t.color(history.bug_id.to_s, :headline)} #{history.bug.summary}") 40 | history.changesets.each do |cs| 41 | text = " #{cs.date} - #{cs.who}" 42 | t.say(t.color(text, :changeset)) 43 | cs.changes.each do |change| 44 | text = " - #{change.field_name} = #{change.removed}" 45 | t.say(t.color(text, :remove)) 46 | text = " + #{change.field_name} = #{change.added}" 47 | t.say(t.color(text, :add)) 48 | end 49 | end 50 | end 51 | 0 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/bicho/cli/commands/reopen.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Klaus Kämpf 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | 28 | require 'bicho/cli/command' 29 | require 'bicho/client' 30 | 31 | module Bicho 32 | module CLI 33 | module Commands 34 | # Command to reopen a bug. 35 | class Reopen < ::Bicho::CLI::Command 36 | options do 37 | opt :comment, 'Comment to add', type: :string 38 | opt :private, 'Set the comment to private', type: :boolean 39 | end 40 | 41 | def do(global_opts, opts, args) 42 | unless opts[:comment] 43 | t.say('Reopen must have a comment') 44 | return 1 45 | end 46 | client = ::Bicho::Client.new(global_opts[:bugzilla]) 47 | client.get_bugs(*args).each do |bug| 48 | id = bug.reopen!(opts[:comment], opts[:private]) 49 | if id == bug.id 50 | t.say("Bug #{id} reopened") 51 | else 52 | t.say("#{t.color('ERROR:', :error)} Failed to reopen bug #{id}") 53 | end 54 | end 55 | 0 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/bicho/cli/commands/search.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | 28 | require 'bicho/cli/command' 29 | require 'bicho/client' 30 | require 'bicho' 31 | require 'bicho/query' 32 | require 'pp' 33 | 34 | module Bicho::CLI::Commands 35 | # Command to search for bugs. 36 | class Search < ::Bicho::CLI::Command 37 | options do 38 | # add all fields as command line options of this command 39 | Bicho::SEARCH_FIELDS.each do |field| 40 | opt field[0], field[2], type: field[1], multi: field[3] 41 | end 42 | opt :format, 'Output format (json, prometheus)', type: :string 43 | end 44 | 45 | def do(global_opts, opts, _args) 46 | server = ::Bicho::Client.new(global_opts[:bugzilla]) 47 | Bicho.client = server 48 | # for most parameter we accept arrays, and also multi mode 49 | # this means parameters come in arrays of arrays 50 | query = ::Bicho::Query.new 51 | opts.each do |n, v| 52 | # skip any option that is not part of SEARCH_FIELDS 53 | next unless Bicho::SEARCH_FIELDS.map { |x| x[0] }.include?(n) 54 | next if v.nil? || v.flatten.empty? 55 | v.flatten.each do |single_val| 56 | query.send(n.to_sym, single_val) 57 | end 58 | end 59 | 60 | case opts[:format] 61 | when 'prometheus' 62 | STDOUT.puts Bicho::Export.to_prometheus_push_gateway(query) 63 | else 64 | server.search_bugs(query).each do |bug| 65 | t.say("#{t.color(bug.id.to_s, :headline)} #{bug.summary}") 66 | end 67 | end 68 | 0 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/bicho/cli/commands/show.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | 28 | require 'bicho/cli/command' 29 | require 'bicho/client' 30 | 31 | module Bicho::CLI::Commands 32 | # Command to display bug information. 33 | class Show < ::Bicho::CLI::Command 34 | options do 35 | opt :format, "Format string, eg. '%{id}:%{priority}:%{summary}'", type: :string 36 | end 37 | 38 | def do(global_opts, opts, args) 39 | client = ::Bicho::Client.new(global_opts[:bugzilla]) 40 | client.get_bugs(*args).each do |bug| 41 | if opts[:format] 42 | t.say(bug.format(opts[:format])) 43 | else 44 | t.say("#{t.color(bug.id.to_s, :headline)} #{bug.summary}") 45 | end 46 | end 47 | 0 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/bicho/cli/commands/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | 28 | require 'bicho/cli/command' 29 | 30 | module Bicho::CLI::Commands 31 | # Command to display Bicho's version. 32 | class Version < ::Bicho::CLI::Command 33 | def do(_global_opts, _opts, _args) 34 | t.say(Bicho::VERSION) 35 | 0 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/bicho/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | require 'inifile' 28 | require 'uri' 29 | require 'xmlrpc/client' 30 | require 'nokogiri' 31 | require 'net/https' 32 | require 'cgi' 33 | 34 | require 'bicho/attachment' 35 | require 'bicho/bug' 36 | require 'bicho/history' 37 | require 'bicho/query' 38 | require 'bicho/logging' 39 | 40 | # Helper IO device that forwards to the logger, we use it 41 | # to debug XMLRPC by monkey patching it 42 | # 43 | # @private 44 | class Bicho::LoggerIODevice 45 | def <<(msg) 46 | Bicho::Logging.logger.debug(msg) 47 | end 48 | end 49 | 50 | # monkey patch XMLRPC 51 | # 52 | # @private 53 | class XMLRPC::Client 54 | def set_debug 55 | @http.set_debug_output(Bicho::LoggerIODevice.new) 56 | end 57 | end 58 | 59 | module Bicho 60 | # Plugins are defined inside this module 61 | module Plugins 62 | end 63 | 64 | # Client to query bugzilla 65 | class Client 66 | include Bicho::Logging 67 | 68 | # @return [URI] XML-RPC API end-point 69 | # 70 | # This URL is automatically inferred from the 71 | # Client#site_url 72 | # 73 | # Plugins can modify the inferred value by providing 74 | # a transform_api_url_hook(url, logger) method returning 75 | # the modified value. 76 | # 77 | attr_reader :api_url 78 | 79 | # @return [URI] Bugzilla installation website 80 | # 81 | # This value is provided at construction time 82 | attr_reader :site_url 83 | 84 | # @return [String] user id, available after login 85 | attr_reader :userid 86 | 87 | # @visibility private 88 | # Implemented only to warn users about the replacement 89 | # APIs 90 | def url 91 | warn 'url is deprecated. Use site_url or api_url' 92 | raise NoMethodError 93 | end 94 | 95 | # @param [String] site_url Bugzilla installation site url 96 | def initialize(site_url) 97 | @plugins = [] 98 | load_plugins! 99 | instantiate_plugins! 100 | 101 | if site_url.nil? 102 | @plugins.each do |pl_instance| 103 | if pl_instance.respond_to?(:default_site_url_hook) 104 | default = pl_instance.default_site_url_hook(logger) 105 | site_url = default unless default.nil? 106 | end 107 | end 108 | end 109 | 110 | # If the default url is still null, we can't continue 111 | raise ArgumentError, 'missing bugzilla site' if site_url.nil? 112 | 113 | @plugins.each do |pl_instance| 114 | if pl_instance.respond_to?(:transform_site_url_hook) 115 | site_url = pl_instance.transform_site_url_hook(site_url, logger) 116 | end 117 | end 118 | 119 | # Don't modify the original url 120 | @site_url = site_url.is_a?(URI) ? site_url.clone : URI.parse(site_url) 121 | 122 | api_url = @site_url.clone 123 | api_url.path = '/xmlrpc.cgi' 124 | 125 | @plugins.each do |pl_instance| 126 | # Modify API url 127 | if pl_instance.respond_to?(:transform_api_url_hook) 128 | api_url = pl_instance.transform_api_url_hook(api_url, logger) 129 | end 130 | end 131 | 132 | @api_url = api_url.is_a?(URI) ? api_url.clone : URI.parse(api_url) 133 | 134 | @client = XMLRPC::Client.new_from_uri(@api_url.to_s, nil, 900) 135 | @client.set_debug 136 | @plugins.each do |pl_instance| 137 | # Modify API url 138 | if pl_instance.respond_to?(:transform_xmlrpc_client_hook) 139 | pl_instance.transform_xmlrpc_client_hook(@client, logger) 140 | end 141 | end 142 | 143 | # User.login sets the credentials cookie for subsequent calls 144 | if @client.user && @client.password 145 | ret = @client.call('User.login', 'login' => @client.user, 'password' => @client.password, 'remember' => 0) 146 | handle_faults(ret) 147 | @userid = ret['id'] 148 | end 149 | end 150 | 151 | # ruby-load all the files in the plugins directory 152 | def load_plugins! 153 | # Scan plugins 154 | plugin_glob = File.join(File.dirname(__FILE__), 'plugins', '*.rb') 155 | Dir.glob(plugin_glob).each do |plugin| 156 | logger.debug("Loading file: #{plugin}") 157 | require plugin 158 | end 159 | end 160 | 161 | # instantiate all plugin classes in the Bicho::Plugins 162 | # module and add them to the list of known plugins 163 | def instantiate_plugins! 164 | ::Bicho::Plugins.constants.each do |cnt| 165 | pl_class = ::Bicho::Plugins.const_get(cnt) 166 | pl_instance = pl_class.new 167 | logger.debug("Loaded: #{pl_instance}") 168 | @plugins << pl_instance 169 | end 170 | end 171 | 172 | def cookie 173 | @client.cookie 174 | end 175 | 176 | def handle_faults(ret) 177 | if ret.key?('faults') 178 | ret['faults'].each do |fault| 179 | logger.error fault 180 | end 181 | end 182 | end 183 | 184 | # Return Bugzilla API version 185 | def version 186 | ret = @client.call('Bugzilla.version') 187 | handle_faults(ret) 188 | ret['version'] 189 | end 190 | 191 | # Create a bug 192 | # 193 | # @param product - the name of the product the bug is being filed against 194 | # @param component - the name of a component in the product above. 195 | # @param summary - a brief description of the bug being filed. 196 | # @param version - version of the product above; the version the bug was found in. 197 | # @param **kwargs - keyword-args containing optional/defaulted params 198 | # 199 | # Return the new bug ID 200 | def create_bug(product, component, summary, version, **kwargs) 201 | params = {} 202 | params = params.merge(kwargs) 203 | params[:product] = product 204 | params[:component] = component 205 | params[:summary] = summary 206 | params[:version] = version 207 | ret = @client.call('Bug.create', params) 208 | handle_faults(ret) 209 | ret['id'] 210 | end 211 | 212 | # Update a bug 213 | # 214 | # @param id - bug number (Integer) or alias (String) of bug to be updated 215 | # @param **kwargs - keyword-args containing optional/defaulted params 216 | # 217 | # @returns id of updated bug 218 | # 219 | def update_bug(id, **kwargs) 220 | params = {} 221 | params = params.merge(kwargs) 222 | params[:ids] = normalize_ids [id] 223 | ret = @client.call('Bug.update', params) 224 | logger.info "Bug.update returned #{ret.inspect}" 225 | handle_faults(ret) 226 | ret.dig('bugs', 0, 'id') 227 | end 228 | 229 | # Search for a bug 230 | # 231 | # +query+ has to be either a +Query+ object or 232 | # a +String+ that will be searched in the summary 233 | # of the bugs. 234 | # 235 | def search_bugs(query) 236 | # allow plain strings to be passed, interpretting them 237 | query = Query.new.summary(query) if query.is_a?(String) 238 | 239 | ret = @client.call('Bug.search', query.query_map) 240 | handle_faults(ret) 241 | bugs = [] 242 | ret['bugs'].each do |bug_data| 243 | bugs << Bug.new(self, bug_data) 244 | end 245 | bugs 246 | end 247 | 248 | # Given a named query's name, runs it 249 | # on the server 250 | # @returns [Array] list of bugs 251 | def expand_named_query(what) 252 | url = @api_url.clone 253 | url.path = '/buglist.cgi' 254 | url.query = "cmdtype=runnamed&namedcmd=#{URI.escape(what)}&ctype=atom" 255 | logger.info("Expanding named query: '#{what}' to #{url.request_uri}") 256 | fetch_named_query_url(url, 5) 257 | end 258 | 259 | # normalize bug ids 260 | # @param ids - array of bug numbers (Integer) or bug aliases (String) 261 | # @returns Array of bug numbers (Integer) 262 | # 263 | # @private 264 | def normalize_ids(ids) 265 | ids.collect(&:to_s).map do |what| 266 | if what =~ /^[0-9]+$/ 267 | next what.to_i 268 | else 269 | next expand_named_query(what) 270 | end 271 | end.flatten 272 | end 273 | 274 | # Fetches a named query by its full url 275 | # @private 276 | # @returns [Array] list of bugs 277 | def fetch_named_query_url(url, redirects_left) 278 | raise 'You need to be authenticated to use named queries' unless @userid 279 | http = Net::HTTP.new(@api_url.host, @api_url.port) 280 | http.set_debug_output(Bicho::LoggerIODevice.new) 281 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 282 | http.use_ssl = (@api_url.scheme == 'https') 283 | # request = Net::HTTP::Get.new(url.request_uri, {'Cookie' => self.cookie}) 284 | request = Net::HTTP::Get.new(url.request_uri) 285 | request.basic_auth @api_url.user, @api_url.password 286 | response = http.request(request) 287 | case response 288 | when Net::HTTPSuccess 289 | bugs = [] 290 | begin 291 | xml = Nokogiri::XML.parse(response.body) 292 | xml.root.xpath('//xmlns:entry/xmlns:link/@href', xml.root.namespace).each do |attr| 293 | uri = URI.parse attr.value 294 | bugs << uri.query.split('=')[1] 295 | end 296 | return bugs 297 | rescue Nokogiri::XML::XPath::SyntaxError 298 | raise "Named query '#{url.request_uri}' not found" 299 | end 300 | when Net::HTTPRedirection 301 | location = response['location'] 302 | if redirects_left.zero? 303 | raise "Maximum redirects exceeded (redirected to #{location})" 304 | end 305 | new_location_uri = URI.parse(location) 306 | logger.debug("Moved to #{new_location_uri}") 307 | fetch_named_query_url(new_location_uri, redirects_left - 1) 308 | else 309 | raise "Error when expanding named query '#{url.request_uri}': #{response}" 310 | end 311 | end 312 | 313 | # Gets a single bug 314 | # @return [Bug] a single bug by id 315 | def get_bug(id) 316 | get_bugs(id).first 317 | end 318 | 319 | # Retrieves one or more bugs by id 320 | # @return [Array] a list of bugs 321 | def get_bugs(*ids) 322 | params = {} 323 | params[:ids] = normalize_ids ids 324 | 325 | bugs = [] 326 | ret = @client.call('Bug.get', params) 327 | handle_faults(ret) 328 | ret['bugs'].each do |bug_data| 329 | bugs << Bug.new(self, bug_data) 330 | end 331 | bugs 332 | end 333 | 334 | # @return [Array] the history of the given bugs 335 | def get_history(*ids) 336 | params = {} 337 | params[:ids] = normalize_ids ids 338 | 339 | histories = [] 340 | ret = @client.call('Bug.history', params) 341 | handle_faults(ret) 342 | ret['bugs'].each do |history_data| 343 | histories << History.new(self, history_data) 344 | end 345 | histories 346 | end 347 | 348 | # @return [Array] a list of attachments for the 349 | # given bugs. 350 | # 351 | # Payload is lazy-loaded 352 | def get_attachments(*ids) 353 | params = {} 354 | params[:ids] = normalize_ids ids 355 | 356 | ret = @client.call('Bug.attachments', 357 | params.merge(exclude_fields: ['data'])) 358 | handle_faults(ret) 359 | ret['bugs'].map do |_, attachments_data| 360 | attachments_data.map do |attachment_data| 361 | Attachment.new(self, @client, attachment_data) 362 | end 363 | end.flatten 364 | end 365 | 366 | # Add an attachment to bugs with given ids 367 | # 368 | # Params: 369 | # @param summary - a short string describing the attachment 370 | # @param file - [File] object to attach 371 | # @param *ids - a list of bug ids to which the attachment will be added 372 | # @param **kwargs - optional keyword-args that may contain: 373 | # - content_type - content type of the attachment (if ommited, 374 | # 'application/octet-stream' will be used) 375 | # - file_name - name of the file (if ommited, the base name of the 376 | # provided file will be used) 377 | # - patch? - flag saying that the attachment is a patch 378 | # - private? - flag saying that the attachment is private 379 | # - comment 380 | # 381 | # @return [Array] a list of the attachment id(s) created. 382 | def add_attachment(summary, file, *ids, **kwargs) 383 | params = {} 384 | params[:ids] = ids 385 | params[:summary] = summary 386 | params[:content_type] = kwargs.fetch(:content_type, 'application/octet-stream') 387 | params[:file_name] = kwargs.fetch(:file_name, File.basename(file)) 388 | params[:is_patch] = kwargs[:patch?] if kwargs[:patch?] 389 | params[:is_private] = kwargs[:private?] if kwargs[:private?] 390 | params[:comment] = kwargs[:comment] if kwargs[:comment] 391 | params[:data] = XMLRPC::Base64.new(file.read) 392 | ret = @client.call('Bug.add_attachment', params) 393 | handle_faults(ret) 394 | ret['ids'] 395 | end 396 | end 397 | end 398 | -------------------------------------------------------------------------------- /lib/bicho/common_client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | require 'logger' 28 | 29 | # All classes go into this module. 30 | module Bicho 31 | # This module allows the [Bug] and other 32 | # classes to offer an ActiveRecord like query 33 | # API by allowing to set a default [Client] 34 | # to make operations. 35 | module CommonClient 36 | class << self 37 | attr_writer :common_client 38 | end 39 | 40 | def self.common_client 41 | @common_client ||= (raise 'No common client set') 42 | end 43 | 44 | def common_client 45 | CommonClient.common_client 46 | end 47 | end 48 | 49 | # Sets the common client to be used by 50 | # the library 51 | def self.client=(client) 52 | CommonClient.common_client = client 53 | end 54 | 55 | # Current client used by the library 56 | def self.client 57 | CommonClient.common_client 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/bicho/export.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | require 'stringio' 5 | 6 | module Bicho 7 | # Utility methods for exporting bugs to other systems 8 | module Export 9 | # Exports full data of a bug to json, including some extended 10 | # calculated attributes. 11 | def self.to_json(bug) 12 | bug_h = bug.to_h 13 | bug_h['history'] = bug.history.changesets.map(&:to_h) 14 | bug_h['resolution_time'] = Bicho::Reports.resolution_time(bug) 15 | JSON.generate(bug_h) 16 | end 17 | 18 | # Export a query for usage as a metric in prometheus 19 | # See https://github.com/prometheus/pushgateway 20 | # 21 | # The metric name will be 'bugs_total' 22 | # See https://prometheus.io/docs/practices/naming/) 23 | # 24 | # And every attributed 25 | # specified in the query will be used as a label 26 | def self.to_prometheus_push_gateway(query) 27 | buf = StringIO.new 28 | dimensions = [:product, :status, :priority, :severity, :resolution, :component] 29 | grouped = query.to_a.group_by do |i| 30 | puts i 31 | dimensions.map { |d| [d, i[d]] }.to_h 32 | end 33 | 34 | buf.write("# TYPE bugs_total gauge\n") 35 | grouped.each do |attrs, set| 36 | labels = attrs 37 | .map { |e| "#{e[0]}=\"#{e[1]}\"" } 38 | .join(',') 39 | buf.write("bugs_total{#{labels}} #{set.size}\n") 40 | end 41 | buf.write("\n") 42 | buf.close 43 | buf.string 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/bicho/ext/logger_colors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Colorizes the output of the standard library logger, depending on the logger level: 4 | # To adjust the colors, look at Logger::Colors::SCHEMA and Logger::Colors::constants 5 | 6 | require 'logger' 7 | 8 | # Utility class to color output. 9 | class Logger 10 | module Colors 11 | VERSION = '1.0.0' 12 | 13 | NOTHING = '0;0' 14 | BLACK = '0;30' 15 | RED = '0;31' 16 | GREEN = '0;32' 17 | BROWN = '0;33' 18 | BLUE = '0;34' 19 | PURPLE = '0;35' 20 | CYAN = '0;36' 21 | LIGHT_GRAY = '0;37' 22 | DARK_GRAY = '1;30' 23 | LIGHT_RED = '1;31' 24 | LIGHT_GREEN = '1;32' 25 | YELLOW = '1;33' 26 | LIGHT_BLUE = '1;34' 27 | LIGHT_PURPLE = '1;35' 28 | LIGHT_CYAN = '1;36' 29 | WHITE = '1;37' 30 | 31 | SCHEMA = { 32 | STDOUT => %w(nothing green brown red purple cyan), 33 | STDERR => %w(nothing green yellow light_red light_purple light_cyan) 34 | }.freeze 35 | end 36 | 37 | alias format_message_colorless format_message 38 | 39 | def format_message(level, *args) 40 | if Logger::Colors::SCHEMA[@logdev.dev] 41 | color = begin 42 | Logger::Colors.const_get \ 43 | Logger::Colors::SCHEMA[@logdev.dev][Logger.const_get(level.sub('ANY', 'UNKNOWN'))].to_s.upcase 44 | rescue NameError 45 | '0;0' 46 | end 47 | "\e[#{color}m#{format_message_colorless(level, *args)}\e[0;0m" 48 | else 49 | format_message_colorless(level, *args) 50 | end 51 | end 52 | end 53 | 54 | # J-_-L 55 | -------------------------------------------------------------------------------- /lib/bicho/history.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | require 'stringio' 28 | 29 | module Bicho 30 | # Represents a single change inside a bug. 31 | # History has multiple ChangeSets, and they have 32 | # multiple changes. 33 | class Change 34 | def field_name 35 | @data['field_name'] 36 | end 37 | 38 | def removed 39 | @data['removed'] 40 | end 41 | 42 | def added 43 | @data['added'] 44 | end 45 | 46 | def to_s 47 | buffer = StringIO.new 48 | buffer << "- #{field_name} = #{removed}\n" 49 | buffer << "+ #{field_name} = #{added}" 50 | buffer.string 51 | end 52 | 53 | def initialize(client, data) 54 | @client = client 55 | @data = data 56 | end 57 | 58 | def to_h 59 | @data 60 | end 61 | end 62 | 63 | # A collection of related changes. 64 | class ChangeSet 65 | include Enumerable 66 | 67 | # return [Date] The date the bug activity/change happened. 68 | # @deprecated Use {#timestamp} instead 69 | def date 70 | warn 'Deprecated. Use timestamp instead' 71 | timestamp 72 | end 73 | 74 | # return [Date] The date the bug activity/change happened. 75 | def timestamp 76 | @data['when'].to_time 77 | end 78 | 79 | # @return [String] The login name of the user who performed the bug change 80 | def who 81 | @data['who'] 82 | end 83 | 84 | # @return [Array] list of changes, with details of what changed 85 | def changes 86 | @data['changes'].map do |change| 87 | Change.new(@client, change) 88 | end 89 | end 90 | 91 | def initialize(client, data) 92 | @client = client 93 | @data = data 94 | end 95 | 96 | def to_s 97 | buffer = StringIO.new 98 | buffer << "#{timestamp}- #{who}\n" 99 | changes.each do |diff| 100 | buffer << "#{diff}\n" 101 | end 102 | buffer.string 103 | end 104 | 105 | def to_h 106 | { who: who, timestamp: timestamp, changes: changes.map(&:to_h) } 107 | end 108 | end 109 | 110 | # A collection of Changesets associated with a bug 111 | class History 112 | include Enumerable 113 | 114 | # iterate over each changeset 115 | def each 116 | return enum_for(:each) unless block_given? 117 | changesets.each do |c| 118 | yield c 119 | end 120 | end 121 | 122 | # @return [Fixnum] number of changesets 123 | def size 124 | changesets.size 125 | end 126 | 127 | # @return [Boolean] true when there are no changesets 128 | def empty? 129 | changesets.empty? 130 | end 131 | 132 | def initialize(client, data) 133 | @client = client 134 | @data = data 135 | end 136 | 137 | def bug_id 138 | @data['id'] 139 | end 140 | 141 | # @return [String] The numeric id of the bug 142 | def bug 143 | @bug = @client.get_bug(@data['id']) unless @bug 144 | @bug 145 | end 146 | 147 | # @return [Array] collection of changesets 148 | def changesets 149 | @data['history'].map do |changeset| 150 | ChangeSet.new(@client, changeset) 151 | end 152 | end 153 | 154 | def to_s 155 | buffer = StringIO.new 156 | buffer << "#{bug_id}\n" 157 | changesets.each do |cs| 158 | buffer << "#{cs}\n" 159 | end 160 | buffer.string 161 | end 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /lib/bicho/logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | require 'logger' 28 | require 'bicho/ext/logger_colors' 29 | 30 | module Bicho 31 | # All logging related classes go into this module. 32 | module Logging 33 | class << self 34 | attr_writer :logger 35 | end 36 | 37 | def self.logger 38 | @logger ||= Logger.new('/dev/null') 39 | end 40 | 41 | def logger 42 | Logging.logger 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/bicho/plugins/aliases.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Bicho 4 | module Plugins 5 | # This plugin allows to specify shortcuts for bugzilla urls 6 | class Aliases 7 | def transform_site_url_hook(url, _logger) 8 | case url.to_s 9 | when 'bko', 'kernel' then 'https://bugzilla.kernel.org' 10 | when 'bgo', 'gnome' then 'https://bugzilla.gnome.org' 11 | when 'kde' then 'https://bugzilla.kde.org' 12 | else url 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/bicho/plugins/novell.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # => 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | require 'inifile' 28 | require 'uri' 29 | 30 | module Bicho 31 | module Plugins 32 | # Novell bugzilla is behind ichain 33 | # 34 | # Plugin that rewrites the bugzilla API url 35 | # to the Novell internal endpoint without 36 | # ichain. 37 | # 38 | # Also, it takes your credentials from 39 | # your oscrc. 40 | # 41 | class Novell 42 | OSCRC_CREDENTIALS = 'https://api.opensuse.org' unless defined? OSCRC_CREDENTIALS 43 | DEFAULT_OSCRC_PATH = File.join(ENV['HOME'], '.oscrc') unless defined? DEFAULT_OSCRC_PATH 44 | DOMAINS = ['bugzilla.novell.com', 'bugzilla.suse.com'].freeze 45 | XMLRPC_DOMAINS = ['apibugzilla.novell.com', 'apibugzilla.suse.com'].freeze 46 | 47 | class << self 48 | attr_writer :oscrc_path 49 | end 50 | 51 | def self.oscrc_path 52 | @oscrc_path ||= DEFAULT_OSCRC_PATH 53 | end 54 | 55 | def to_s 56 | self.class.to_s 57 | end 58 | 59 | def self.oscrc_credentials 60 | oscrc = IniFile.load(oscrc_path) 61 | urls = [OSCRC_CREDENTIALS] 62 | urls << "#{OSCRC_CREDENTIALS}/" unless OSCRC_CREDENTIALS.end_with?('/') 63 | urls.each do |section| 64 | next unless oscrc.has_section?(section) 65 | user = oscrc[section]['user'] 66 | pass = oscrc[section]['pass'] 67 | return { user: user, password: pass } if user && pass 68 | end 69 | raise "No valid .oscrc credentials for Novell/SUSE bugzilla (#{oscrc_path})" 70 | end 71 | 72 | def transform_site_url_hook(url, _logger) 73 | case url.to_s 74 | when 'bnc', 'novell' then 'https://bugzilla.novell.com' 75 | when 'bsc', 'suse' then 'https://bugzilla.suse.com' 76 | when 'boo', 'opensuse' then 'https://bugzilla.opensuse.org' 77 | else url 78 | end 79 | end 80 | 81 | def transform_api_url_hook(url, logger) 82 | return url unless DOMAINS.map { |domain| url.host&.include?(domain) }.any? 83 | 84 | begin 85 | url = url.clone 86 | url.host = url.host.gsub(/bugzilla\.novell.com/, 'apibugzilla.novell.com') 87 | url.host = url.host.gsub(/bugzilla\.suse.com/, 'apibugzilla.suse.com') 88 | url.scheme = 'https' 89 | 90 | logger.debug("#{self} : Rewrote url to '#{url}'") 91 | rescue StandardError => e 92 | logger.warn e 93 | end 94 | url 95 | end 96 | 97 | def transform_xmlrpc_client_hook(client, logger) 98 | return unless XMLRPC_DOMAINS.map { |domain| client.http.address.include?(domain) }.any? 99 | 100 | auth = Novell.oscrc_credentials 101 | client.user = auth[:user] 102 | client.password = auth[:password] 103 | logger.debug("#{self} : updated XMLRPC client with oscrc auth information") 104 | rescue StandardError => e 105 | logger.error e 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/bicho/plugins/user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'yaml' 4 | 5 | module Bicho 6 | module Plugins 7 | # Plugin to get preferences from the user home directory 8 | # .config/bicho/config.yml 9 | # 10 | class User 11 | DEFAULT_CONFIG_PATH = File.join(ENV['HOME'], '.config', 'bicho', 'config.yml') unless defined? DEFAULT_CONFIG_PATH 12 | 13 | class << self 14 | attr_writer :config_path 15 | end 16 | 17 | def self.config_path 18 | @config_path ||= DEFAULT_CONFIG_PATH 19 | end 20 | 21 | def initialize 22 | @config = {} 23 | @config = YAML.load_file(Bicho::Plugins::User.config_path) if File.exist?(Bicho::Plugins::User.config_path) 24 | end 25 | 26 | def default_site_url_hook(logger) 27 | if @config.key?('default') 28 | ret = @config['default'] 29 | logger.debug "Default url set to '#{ret}'" 30 | ret 31 | else 32 | logger.warn 'Use .config/bicho/config.yaml to setup a default bugzilla site' 33 | end 34 | end 35 | 36 | def transform_site_url_hook(url, logger) 37 | if @config['aliases']&.key?(url.to_s) 38 | ret = @config['aliases'][url.to_s] 39 | logger.debug "Transformed '#{url}' to '#{ret}'" 40 | ret 41 | else 42 | url 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/bicho/query.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | #-- 4 | # Copyright (c) 2011 SUSE LINUX Products GmbH 5 | # 6 | # Author: Duncan Mac-Vicar P. 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining 9 | # a copy of this software and associated documentation files (the 10 | # "Software"), to deal in the Software without restriction, including 11 | # without limitation the rights to use, copy, modify, merge, publish, 12 | # distribute, sublicense, and/or sell copies of the Software, and to 13 | # permit persons to whom the Software is furnished to do so, subject to 14 | # the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be 17 | # included in all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 23 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 25 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | #++ 27 | require 'bicho/common_client' 28 | 29 | module Bicho 30 | # Represents a bug search to the server and it can 31 | # be configured with all bug attributes. 32 | # 33 | # 34 | class Query 35 | include Enumerable 36 | 37 | # Iterates through the result of the current query. 38 | # 39 | # @note Requires Bicho.client to be set 40 | # 41 | # @yield [Bicho::Bug] 42 | def each 43 | ret = Bicho.client.search_bugs(self) 44 | return ret.each unless block_given? 45 | ret.each { |bug| yield bug } 46 | end 47 | 48 | # obtains the parameter that can be passed to the XMLRPC API 49 | # @private 50 | attr_reader :query_map 51 | 52 | # Create a query. 53 | # 54 | # @example query from a hash containing the attributes: 55 | # q = Query.new({:summary => "substring", :assigned_to => "foo@bar.com"}) 56 | # 57 | # @example using chainable methods: 58 | # q = Query.new.assigned_to("foo@bar.com@).summary("some text") 59 | # 60 | def initialize(conditions = {}) 61 | @query_map = conditions 62 | end 63 | 64 | # Query responds to all the bug search attributes. 65 | # 66 | # @see {Bug.where Allowed attributes} 67 | def method_missing(method_name, *args) 68 | return super unless Bicho::SEARCH_FIELDS 69 | .map(&:first) 70 | .include?(method_name) 71 | args.each do |arg| 72 | append_query(method_name.to_s, arg) 73 | end 74 | self 75 | end 76 | 77 | def respond_to_missing?(method_name, _include_private = false) 78 | Bicho::SEARCH_FIELDS.map(&:first).include?(method_name) || super 79 | end 80 | 81 | # Shortcut equivalent to status new, assigned, needinfo, reopened, confirmed, and in_progress 82 | def open 83 | status(:new).status(:assigned).status(:needinfo).status(:reopened).status(:confirmed).status(:in_progress) 84 | end 85 | 86 | # Shortcut, equivalent to 87 | # :summary => "L3" 88 | def L3 # rubocop:disable Naming/MethodName 89 | append_query('summary', 'L3') 90 | self 91 | end 92 | 93 | private 94 | 95 | # Appends a parameter to the query map 96 | # 97 | # Only used internally. 98 | # 99 | # If the parameter already exists that parameter is converted to an 100 | # array of values 101 | # 102 | # @private 103 | def append_query(param, value) 104 | @query_map[param] = [] unless @query_map.key?(param) 105 | @query_map[param] = [@query_map[param], value].flatten 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/bicho/reports.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Bicho 4 | # Utility methods for reporting on bugs 5 | module Reports 6 | # When was the bug finally set to a resolved state 7 | # 8 | # Resolution time is nil if the bug is not resolved yet. 9 | def self.resolution_time(bug) 10 | t = nil 11 | bug.history.sort_by(&:timestamp).each do |cs| 12 | cs.changes.each do |c| 13 | t = cs.timestamp if c.field_name == 'status' && 14 | c.added == 'RESOLVED' 15 | end 16 | end 17 | t 18 | end 19 | 20 | # returns the ranges a bug is with statuses 21 | def self.ranges_with_statuses(bug, *statuses) 22 | ranges = [] 23 | current_start = bug.creation_time 24 | current_status = nil 25 | bug.history.sort_by(&:timestamp).each do |cs| 26 | cs.changes.each do |c| 27 | next unless c.field_name == 'status' 28 | 29 | current_status = c.removed if current_status.nil? 30 | ranges.push(current_start..cs.timestamp) if statuses.include?(c.removed) 31 | 32 | current_start = cs.timestamp 33 | current_status = c.added 34 | end 35 | end 36 | # last status is still valid 37 | ranges.push(current_start..Time.now) if statuses.include?(current_status) 38 | ranges 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/bicho/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Bicho 4 | VERSION = '0.0.18' 5 | end 6 | -------------------------------------------------------------------------------- /test/fixtures/777777.json: -------------------------------------------------------------------------------- 1 | {"priority":"Normal","blocks":[],"creator":"kekun.plazas@laposte.net","last_change_time":"2017-02-22 20:48:43 UTC","is_cc_accessible":true,"keywords":[],"cc":["kekun.plazas@laposte.net","mcatanzaro@gnome.org"],"url":"","assigned_to":"gnome-games-maint@gnome.bugs","groups":[],"see_also":[],"id":777777,"creation_time":"2017-01-26 08:46:00 UTC","whiteboard":"","qa_contact":"gnome-games-maint@gnome.bugs","depends_on":[],"resolution":"FIXED","classification":"Core","op_sys":"Linux","status":"RESOLVED","cf_gnome_target":"---","cf_gnome_version":"---","summary":"Game Boy games not detected","is_open":false,"platform":"Other","severity":"normal","flags":[],"version":"unspecified","component":"general","is_creator_accessible":true,"product":"gnome-games","is_confirmed":true,"target_milestone":"---","history":[{"who":"kekun.plazas@laposte.net","timestamp":"2017-01-26 09:03:10 UTC","changes":[{"removed":"","added":"kekun.plazas@laposte.net","field_name":"cc"}]},{"who":"kekun.plazas@laposte.net","timestamp":"2017-01-26 09:05:34 UTC","changes":[{"attachment_id":344292,"removed":"0","added":"1","field_name":"attachments.isobsolete"}]},{"who":"kekun.plazas@laposte.net","timestamp":"2017-01-26 09:06:09 UTC","changes":[{"removed":"NEW","added":"RESOLVED","field_name":"status"},{"removed":"","added":"FIXED","field_name":"resolution"}]},{"who":"kekun.plazas@laposte.net","timestamp":"2017-01-26 09:06:13 UTC","changes":[{"attachment_id":344293,"removed":"none","added":"committed","field_name":"attachments.gnome_attachment_status"}]},{"who":"kekun.plazas@laposte.net","timestamp":"2017-01-26 09:06:16 UTC","changes":[{"attachment_id":344294,"removed":"none","added":"committed","field_name":"attachments.gnome_attachment_status"}]},{"who":"kekun.plazas@laposte.net","timestamp":"2017-01-26 11:06:35 UTC","changes":[{"attachment_id":344303,"removed":"none","added":"committed","field_name":"attachments.gnome_attachment_status"}]},{"who":"mcatanzaro@gnome.org","timestamp":"2017-02-22 20:40:19 UTC","changes":[{"removed":"","added":"mcatanzaro@gnome.org","field_name":"cc"}]}],"resolution_time":"2017-01-26 09:06:09 UTC"} -------------------------------------------------------------------------------- /test/fixtures/bugzilla_gnome_org_search_prometheus_export.txt: -------------------------------------------------------------------------------- 1 | # TYPE bugs_total gauge 2 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="enhancement",resolution="",component="general"} 89 3 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="enhancement",resolution="",component="Keybindings"} 7 4 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="normal",resolution="",component="general"} 43 5 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="enhancement",resolution="",component="Profiles"} 13 6 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="normal",resolution="",component="Keybindings"} 5 7 | bugs_total{product="gnome-terminal",status="ASSIGNED",priority="Normal",severity="enhancement",resolution="",component="docs"} 1 8 | bugs_total{product="gnome-terminal",status="REOPENED",priority="Normal",severity="enhancement",resolution="",component="general"} 1 9 | bugs_total{product="gnome-terminal",status="REOPENED",priority="Normal",severity="minor",resolution="",component="general"} 1 10 | bugs_total{product="gnome-terminal",status="NEW",priority="High",severity="major",resolution="",component="general"} 1 11 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="normal",resolution="",component="Profiles"} 8 12 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="minor",resolution="",component="general"} 9 13 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="major",resolution="",component="general"} 4 14 | bugs_total{product="gnome-terminal",status="REOPENED",priority="Normal",severity="normal",resolution="",component="general"} 3 15 | bugs_total{product="gnome-terminal",status="NEW",priority="Low",severity="normal",resolution="",component="docs"} 1 16 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="critical",resolution="",component="general"} 1 17 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="enhancement",resolution="",component="docs"} 1 18 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="minor",resolution="",component="Profiles"} 3 19 | bugs_total{product="gnome-terminal",status="REOPENED",priority="Normal",severity="normal",resolution="",component="Profiles"} 1 20 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="minor",resolution="",component="Keybindings"} 2 21 | bugs_total{product="gnome-terminal",status="NEW",priority="Normal",severity="normal",resolution="",component="docs"} 2 22 | bugs_total{product="vala",status="NEW",priority="Normal",severity="normal",resolution="",component="general"} 1 23 | 24 | -------------------------------------------------------------------------------- /test/fixtures/vcr/bugzilla_gnome_org_312619.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 6 | body: 7 | encoding: UTF-8 8 | string: 'Bug.getids312619 9 | 10 | ' 11 | headers: 12 | User-Agent: 13 | - XMLRPC::Client (Ruby 2.5.0) 14 | Content-Type: 15 | - text/xml; charset=utf-8 16 | Content-Length: 17 | - '250' 18 | Connection: 19 | - keep-alive 20 | Accept-Encoding: 21 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 22 | Accept: 23 | - "*/*" 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Date: 30 | - Tue, 23 Oct 2018 10:53:59 GMT 31 | Server: 32 | - Apache/2.4.6 (Red Hat Enterprise Linux) 33 | X-Xss-Protection: 34 | - 1; mode=block 35 | X-Frame-Options: 36 | - SAMEORIGIN 37 | X-Content-Type-Options: 38 | - nosniff 39 | Set-Cookie: 40 | - Bugzilla_login_request_cookie=3NhILz4YdV; path=/; secure; HttpOnly 41 | Content-Length: 42 | - '3051' 43 | Content-Type: 44 | - text/xml 45 | Soapserver: 46 | - SOAP::Lite/Perl/1.1 47 | Strict-Transport-Security: 48 | - max-age=31536000; includeSubDomains; preload 49 | Access-Control-Allow-Origin: 50 | - https://bugzilla.gnome.org 51 | Connection: 52 | - close 53 | body: 54 | encoding: UTF-8 55 | string: faultsbugspriorityNormalblockscreatorcbarton@metavr.comlast_change_time20060411T19:22:41is_cc_accessible1keywordsccploum@ploum.neturlassigned_toepiphany-maint@gnome.bugsgroupssee_alsoid312619creation_time20050804T22:31:00whiteboardqa_contactmpgritti@gmail.comdepends_onresolutionINCOMPLETEclassificationCoreop_sysLinuxstatusRESOLVEDcf_gnome_target---cf_gnome_version2.9/2.10summaryno 64 | input boxes display in epiphany after screem crashis_open0platformOtherseveritynormalflagsversion1.6.xcomponent[obsolete] 66 | Backend:Mozillais_creator_accessible1productepiphanyis_confirmed0target_milestone--- 67 | http_version: 68 | recorded_at: Tue, 23 Oct 2018 10:54:00 GMT 69 | - request: 70 | method: post 71 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 72 | body: 73 | encoding: UTF-8 74 | string: 'Bug.historyids312619 75 | 76 | ' 77 | headers: 78 | User-Agent: 79 | - XMLRPC::Client (Ruby 2.5.0) 80 | Content-Type: 81 | - text/xml; charset=utf-8 82 | Content-Length: 83 | - '254' 84 | Connection: 85 | - keep-alive 86 | Cookie: 87 | - Bugzilla_login_request_cookie=3NhILz4YdV 88 | Accept-Encoding: 89 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 90 | Accept: 91 | - "*/*" 92 | response: 93 | status: 94 | code: 200 95 | message: OK 96 | headers: 97 | Date: 98 | - Tue, 23 Oct 2018 10:54:00 GMT 99 | Server: 100 | - Apache/2.4.6 (Red Hat Enterprise Linux) 101 | X-Xss-Protection: 102 | - 1; mode=block 103 | X-Frame-Options: 104 | - SAMEORIGIN 105 | X-Content-Type-Options: 106 | - nosniff 107 | Content-Length: 108 | - '2612' 109 | Content-Type: 110 | - text/xml 111 | Soapserver: 112 | - SOAP::Lite/Perl/1.1 113 | Strict-Transport-Security: 114 | - max-age=31536000; includeSubDomains; preload 115 | Access-Control-Allow-Origin: 116 | - https://bugzilla.gnome.org 117 | Connection: 118 | - close 119 | body: 120 | encoding: UTF-8 121 | string: bugshistorywhen20050808T12:24:08changesremovedepiphanyaddedno 122 | input boxes display in epiphany after screem crashfield_namesummarywhoploum@ploum.netwhen20051231T21:56:36changesremovedUNCONFIRMEDaddedNEEDINFOfield_namestatuswhochpe@gnome.orgwhen20060411T19:22:41changesremovedaddedploum@fritalk.comfield_nameccremovedNEEDINFOaddedRESOLVEDfield_namestatusremovedaddedINCOMPLETEfield_nameresolutionwhoploum@ploum.netid312619 125 | http_version: 126 | recorded_at: Tue, 23 Oct 2018 10:54:01 GMT 127 | - request: 128 | method: post 129 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 130 | body: 131 | encoding: UTF-8 132 | string: 'Bug.historyids312619 133 | 134 | ' 135 | headers: 136 | User-Agent: 137 | - XMLRPC::Client (Ruby 2.5.0) 138 | Content-Type: 139 | - text/xml; charset=utf-8 140 | Content-Length: 141 | - '254' 142 | Connection: 143 | - keep-alive 144 | Cookie: 145 | - Bugzilla_login_request_cookie=3NhILz4YdV 146 | Accept-Encoding: 147 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 148 | Accept: 149 | - "*/*" 150 | response: 151 | status: 152 | code: 200 153 | message: OK 154 | headers: 155 | Date: 156 | - Tue, 23 Oct 2018 10:54:01 GMT 157 | Server: 158 | - Apache/2.4.6 (Red Hat Enterprise Linux) 159 | X-Xss-Protection: 160 | - 1; mode=block 161 | X-Frame-Options: 162 | - SAMEORIGIN 163 | X-Content-Type-Options: 164 | - nosniff 165 | Content-Length: 166 | - '2612' 167 | Content-Type: 168 | - text/xml 169 | Soapserver: 170 | - SOAP::Lite/Perl/1.1 171 | Strict-Transport-Security: 172 | - max-age=31536000; includeSubDomains; preload 173 | Access-Control-Allow-Origin: 174 | - https://bugzilla.gnome.org 175 | Connection: 176 | - close 177 | body: 178 | encoding: UTF-8 179 | string: bugshistorywhen20050808T12:24:08changesremovedepiphanyaddedno 180 | input boxes display in epiphany after screem crashfield_namesummarywhoploum@ploum.netwhen20051231T21:56:36changesremovedUNCONFIRMEDaddedNEEDINFOfield_namestatuswhochpe@gnome.orgwhen20060411T19:22:41changesremovedaddedploum@fritalk.comfield_nameccremovedNEEDINFOaddedRESOLVEDfield_namestatusremovedaddedINCOMPLETEfield_nameresolutionwhoploum@ploum.netid312619 183 | http_version: 184 | recorded_at: Tue, 23 Oct 2018 10:54:02 GMT 185 | recorded_with: VCR 4.0.0 186 | -------------------------------------------------------------------------------- /test/fixtures/vcr/bugzilla_gnome_org_645150_history.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 6 | body: 7 | encoding: UTF-8 8 | string: 'Bug.getids645150 9 | 10 | ' 11 | headers: 12 | User-Agent: 13 | - XMLRPC::Client (Ruby 2.5.0) 14 | Content-Type: 15 | - text/xml; charset=utf-8 16 | Content-Length: 17 | - '250' 18 | Connection: 19 | - keep-alive 20 | Accept-Encoding: 21 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 22 | Accept: 23 | - "*/*" 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Date: 30 | - Tue, 23 Oct 2018 10:50:54 GMT 31 | Server: 32 | - Apache/2.4.6 (Red Hat Enterprise Linux) 33 | X-Xss-Protection: 34 | - 1; mode=block 35 | X-Frame-Options: 36 | - SAMEORIGIN 37 | X-Content-Type-Options: 38 | - nosniff 39 | Set-Cookie: 40 | - Bugzilla_login_request_cookie=VyvD2YuI0S; path=/; secure; HttpOnly 41 | Content-Length: 42 | - '3146' 43 | Content-Type: 44 | - text/xml 45 | Soapserver: 46 | - SOAP::Lite/Perl/1.1 47 | Strict-Transport-Security: 48 | - max-age=31536000; includeSubDomains; preload 49 | Access-Control-Allow-Origin: 50 | - https://bugzilla.gnome.org 51 | Connection: 52 | - close 53 | body: 54 | encoding: UTF-8 55 | string: faultsbugspriorityNormalblockscreatoradam@medovina.orglast_change_time20110402T18:32:21is_cc_accessible1keywordscceric@yorba.orgj@bitron.chjim@yorba.orglucas@yorba.orgurlassigned_tovala-maint@gnome.bugsgroupssee_alsoid645150creation_time20110318T15:28:00whiteboardqa_contactvala-maint@gnome.bugsdepends_onresolutionFIXEDclassificationCoreop_sysAllstatusRESOLVEDcf_gnome_target---cf_gnome_version---summarystring 64 | method to iterate over charactersis_open0platformOtherseveritynormalflagsversionunspecifiedcomponentBasic 66 | Typesis_creator_accessible1productvalais_confirmed1target_milestone0.12 67 | http_version: 68 | recorded_at: Tue, 23 Oct 2018 10:50:56 GMT 69 | - request: 70 | method: post 71 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 72 | body: 73 | encoding: UTF-8 74 | string: 'Bug.historyids645150 75 | 76 | ' 77 | headers: 78 | User-Agent: 79 | - XMLRPC::Client (Ruby 2.5.0) 80 | Content-Type: 81 | - text/xml; charset=utf-8 82 | Content-Length: 83 | - '254' 84 | Connection: 85 | - keep-alive 86 | Cookie: 87 | - Bugzilla_login_request_cookie=VyvD2YuI0S 88 | Accept-Encoding: 89 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 90 | Accept: 91 | - "*/*" 92 | response: 93 | status: 94 | code: 200 95 | message: OK 96 | headers: 97 | Date: 98 | - Tue, 23 Oct 2018 10:50:55 GMT 99 | Server: 100 | - Apache/2.4.6 (Red Hat Enterprise Linux) 101 | X-Xss-Protection: 102 | - 1; mode=block 103 | X-Frame-Options: 104 | - SAMEORIGIN 105 | X-Content-Type-Options: 106 | - nosniff 107 | Content-Length: 108 | - '4238' 109 | Content-Type: 110 | - text/xml 111 | Soapserver: 112 | - SOAP::Lite/Perl/1.1 113 | Strict-Transport-Security: 114 | - max-age=31536000; includeSubDomains; preload 115 | Access-Control-Allow-Origin: 116 | - https://bugzilla.gnome.org 117 | Connection: 118 | - close 119 | body: 120 | encoding: UTF-8 121 | string: bugshistorywhen20110318T15:28:51changesremovedaddederic@yorba.org, 123 | jim@yorba.org, lucas@yorba.orgfield_nameccwhoadam@medovina.orgwhen20110318T15:43:30changesremovedUNCONFIRMEDaddedNEEDINFOfield_namestatusremovedaddedj@bitron.chfield_nameccwhoj@bitron.chwhen20110322T20:00:31changesremovedNEEDINFOaddedNEWfield_namestatusremoved---added0.12field_nametarget_milestoneremovedstring 125 | method that returns all characters spanning range from s to taddedstring 126 | method to iterate over charactersfield_namesummaryremoved0added1field_nameis_confirmedremovedLinuxaddedAllfield_nameop_syswhoj@bitron.chwhen20110402T18:32:21changesremovedNEWaddedRESOLVEDfield_namestatusremovedaddedFIXEDfield_nameresolutionwhoj@bitron.chid645150 128 | http_version: 129 | recorded_at: Tue, 23 Oct 2018 10:50:56 GMT 130 | recorded_with: VCR 4.0.0 131 | -------------------------------------------------------------------------------- /test/fixtures/vcr/bugzilla_gnome_org_679745_attachments.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 6 | body: 7 | encoding: UTF-8 8 | string: 'Bug.attachmentsids679745exclude_fieldsdata 9 | 10 | ' 11 | headers: 12 | User-Agent: 13 | - XMLRPC::Client (Ruby 2.5.0) 14 | Content-Type: 15 | - text/xml; charset=utf-8 16 | Content-Length: 17 | - '381' 18 | Connection: 19 | - keep-alive 20 | Accept-Encoding: 21 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 22 | Accept: 23 | - "*/*" 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Date: 30 | - Tue, 23 Oct 2018 10:50:50 GMT 31 | Server: 32 | - Apache/2.4.6 (Red Hat Enterprise Linux) 33 | X-Xss-Protection: 34 | - 1; mode=block 35 | X-Frame-Options: 36 | - SAMEORIGIN 37 | X-Content-Type-Options: 38 | - nosniff 39 | Set-Cookie: 40 | - Bugzilla_login_request_cookie=YUL53GJNuK; path=/; secure; HttpOnly 41 | Content-Length: 42 | - '1730' 43 | Content-Type: 44 | - text/xml 45 | Soapserver: 46 | - SOAP::Lite/Perl/1.1 47 | Strict-Transport-Security: 48 | - max-age=31536000; includeSubDomains; preload 49 | Access-Control-Allow-Origin: 50 | - https://bugzilla.gnome.org 51 | Connection: 52 | - close 53 | body: 54 | encoding: UTF-8 55 | string: 'attachmentsbugs679745is_private0flagscreatorfelipeborges@gnome.orglast_change_time20160613T13:38:14bug_id679745descriptionuser-accounts: 58 | use Password Login instead of Automatic Loginsize8559attacherfelipeborges@gnome.orgcontent_typetext/plainfile_nameuser-accounts-use-Password-Login-instead-of-Automa.patchsummaryuser-accounts: 59 | use Password Login instead of Automatic Loginid329690creation_time20160613T12:34:00is_patch1is_obsolete0' 60 | http_version: 61 | recorded_at: Tue, 23 Oct 2018 10:50:51 GMT 62 | - request: 63 | method: post 64 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 65 | body: 66 | encoding: UTF-8 67 | string: 'Bug.attachmentsattachment_ids329690include_fieldsdata 68 | 69 | ' 70 | headers: 71 | User-Agent: 72 | - XMLRPC::Client (Ruby 2.5.0) 73 | Content-Type: 74 | - text/xml; charset=utf-8 75 | Content-Length: 76 | - '392' 77 | Connection: 78 | - keep-alive 79 | Cookie: 80 | - Bugzilla_login_request_cookie=YUL53GJNuK 81 | Accept-Encoding: 82 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 83 | Accept: 84 | - "*/*" 85 | response: 86 | status: 87 | code: 200 88 | message: OK 89 | headers: 90 | Date: 91 | - Tue, 23 Oct 2018 10:50:50 GMT 92 | Server: 93 | - Apache/2.4.6 (Red Hat Enterprise Linux) 94 | X-Xss-Protection: 95 | - 1; mode=block 96 | X-Frame-Options: 97 | - SAMEORIGIN 98 | X-Content-Type-Options: 99 | - nosniff 100 | Content-Length: 101 | - '11813' 102 | Content-Type: 103 | - text/xml 104 | Soapserver: 105 | - SOAP::Lite/Perl/1.1 106 | Strict-Transport-Security: 107 | - max-age=31536000; includeSubDomains; preload 108 | Access-Control-Allow-Origin: 109 | - https://bugzilla.gnome.org 110 | Connection: 111 | - close 112 | body: 113 | encoding: UTF-8 114 | string: attachments329690dataRnJvbSAzYTYzYWNlZDAwZDEyN2ZjMmM1ZDAzMTFmOTU4YTYzZTAzMTg3YTMwIE1vbiBTZXAgMTcgMDA6MDA6MDAgMjAwMQpGcm9tOiBGZWxpcGUgQm9yZ2VzIDxmZWxpcGVib3JnZXNAZ25vbWUub3JnPgpEYXRlOiBNb24sIDEzIEp1biAyMDE2IDEzOjU0OjI0ICswMjAwClN1YmplY3Q6IFtQQVRDSF0gdXNlci1hY2NvdW50czogdXNlIFBhc3N3b3JkIExvZ2luIGluc3RlYWQgb2YgQXV0b21hdGljIExvZ2luCgpwYXNzd29yZF9sb2dpbiA9PSAhYXV0b21hdGljX2xvZ2luCmh0dHBzOi8vd2lraS5nbm9tZS5vcmcvRGVzaWduL1N5c3RlbVNldHRpbmdzL1VzZXJBY2NvdW50cwoKaHR0cHM6Ly9idWd6aWxsYS5nbm9tZS5vcmcvc2hvd19idWcuY2dpP2lkPTc2NzA2NQoKaHR0cHM6Ly9idWd6aWxsYS5nbm9tZS5vcmcvc2hvd19idWcuY2dpP2lkPTY3OTc0NQotLS0KIHBhbmVscy91c2VyLWFjY291bnRzL2RhdGEvdXNlci1hY2NvdW50cy1kaWFsb2cudWkgfCAxNCArKysrLS0tLS0KIHBhbmVscy91c2VyLWFjY291bnRzL3VtLXVzZXItcGFuZWwuYyAgICAgICAgICAgICAgfCAzOCArKysrKysrKysrKy0tLS0tLS0tLS0tLQogMiBmaWxlcyBjaGFuZ2VkLCAyNiBpbnNlcnRpb25zKCspLCAyNiBkZWxldGlvbnMoLSkKCmRpZmYgLS1naXQgYS9wYW5lbHMvdXNlci1hY2NvdW50cy9kYXRhL3VzZXItYWNjb3VudHMtZGlhbG9nLnVpIGIvcGFuZWxzL3VzZXItYWNjb3VudHMvZGF0YS91c2VyLWFjY291bnRzLWRpYWxvZy51aQppbmRleCBjOWYzNjIxLi4zODM1ODNhIDEwMDY0NAotLS0gYS9wYW5lbHMvdXNlci1hY2NvdW50cy9kYXRhL3VzZXItYWNjb3VudHMtZGlhbG9nLnVpCisrKyBiL3BhbmVscy91c2VyLWFjY291bnRzL2RhdGEvdXNlci1hY2NvdW50cy1kaWFsb2cudWkKQEAgLTI3MiwxMiArMjcyLDEyIEBACiAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvcGFja2luZz4KICAgICAgICAgICAgICAgICAgICAgICAgIDwvY2hpbGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICA8Y2hpbGQ+Ci0gICAgICAgICAgICAgICAgICAgICAgICAgIDxvYmplY3QgY2xhc3M9Ikd0a0xhYmVsIiBpZD0iYXV0b2xvZ2luLWxhYmVsIj4KKyAgICAgICAgICAgICAgICAgICAgICAgICAgPG9iamVjdCBjbGFzcz0iR3RrTGFiZWwiIGlkPSJwYXNzd29yZC1sb2dpbi1sYWJlbCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHByb3BlcnR5IG5hbWU9InZpc2libGUiPlRydWU8L3Byb3BlcnR5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJ4YWxpZ24iPjE8L3Byb3BlcnR5PgotICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJsYWJlbCIgdHJhbnNsYXRhYmxlPSJ5ZXMiPkFfdXRvbWF0aWMgTG9naW48L3Byb3BlcnR5PgorICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJsYWJlbCIgdHJhbnNsYXRhYmxlPSJ5ZXMiPlBhX3Nzd29yZCBMb2dpbjwvcHJvcGVydHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHByb3BlcnR5IG5hbWU9InVzZV91bmRlcmxpbmUiPlRydWU8L3Byb3BlcnR5PgotICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJtbmVtb25pY193aWRnZXQiPmF1dG9sb2dpbi1zd2l0Y2g8L3Byb3BlcnR5PgorICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJtbmVtb25pY193aWRnZXQiPnBhc3N3b3JkLWxvZ2luLXN3aXRjaDwvcHJvcGVydHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHN0eWxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNsYXNzIG5hbWU9ImRpbS1sYWJlbCIvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvc3R5bGU+CkBAIC0yOTAsMTEgKzI5MCwxMSBAQAogICAgICAgICAgICAgICAgICAgICAgICAgICA8L3BhY2tpbmc+CiAgICAgICAgICAgICAgICAgICAgICAgICA8L2NoaWxkPgogICAgICAgICAgICAgICAgICAgICAgICAgPGNoaWxkPgotICAgICAgICAgICAgICAgICAgICAgICAgICA8b2JqZWN0IGNsYXNzPSJHdGtCb3giIGlkPSJhdXRvbG9naW4tYm94Ij4KKyAgICAgICAgICAgICAgICAgICAgICAgICAgPG9iamVjdCBjbGFzcz0iR3RrQm94IiBpZD0icGFzc3dvcmQtbG9naW4tYm94Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cHJvcGVydHkgbmFtZT0idmlzaWJsZSI+VHJ1ZTwvcHJvcGVydHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHByb3BlcnR5IG5hbWU9Im9yaWVudGF0aW9uIj5HVEtfT1JJRU5UQVRJT05fSE9SSVpPTlRBTDwvcHJvcGVydHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGNoaWxkPgotICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPG9iamVjdCBjbGFzcz0iR3RrU3dpdGNoIiBpZD0iYXV0b2xvZ2luLXN3aXRjaCI+CisgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8b2JqZWN0IGNsYXNzPSJHdGtTd2l0Y2giIGlkPSJwYXNzd29yZC1sb2dpbi1zd2l0Y2giPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cHJvcGVydHkgbmFtZT0idmlzaWJsZSI+VHJ1ZTwvcHJvcGVydHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwcm9wZXJ0eSBuYW1lPSJ2YWxpZ24iPkdUS19BTElHTl9DRU5URVI8L3Byb3BlcnR5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9vYmplY3Q+CkBAIC01MjAsNyArNTIwLDcgQEAKICAgICAgIDx3aWRnZXQgbmFtZT0ibGFuZ3VhZ2UtbGFiZWwiLz4KICAgICAgIDx3aWRnZXQgbmFtZT0icGFzc3dvcmQtbGFiZWwiLz4KICAgICAgIDx3aWRnZXQgbmFtZT0iYWNjb3VudC10eXBlLWxhYmVsIi8+Ci0gICAgICA8d2lkZ2V0IG5hbWU9ImF1dG9sb2dpbi1sYWJlbCIvPgorICAgICAgPHdpZGdldCBuYW1lPSJwYXNzd29yZC1sb2dpbi1sYWJlbCIvPgogICAgICAgPHdpZGdldCBuYW1lPSJsYXN0LWxvZ2luLWxhYmVsIi8+CiAgICAgPC93aWRnZXRzPgogICA8L29iamVjdD4KQEAgLTUzMiw3ICs1MzIsNyBAQAogICAgICAgPHdpZGdldCBuYW1lPSJhY2NvdW50LXBhc3N3b3JkLWJ1dHRvbiIvPgogICAgICAgPHdpZGdldCBuYW1lPSJhY2NvdW50LXR5cGUtYm94Ii8+CiAgICAgICA8d2lkZ2V0IG5hbWU9ImFjY291bnQtdHlwZS1zdGF0aWMiLz4KLSAgICAgIDx3aWRnZXQgbmFtZT0iYXV0b2xvZ2luLWJveCIvPgorICAgICAgPHdpZGdldCBuYW1lPSJwYXNzd29yZC1sb2dpbi1ib3giLz4KICAgICAgIDx3aWRnZXQgbmFtZT0ibGFzdC1sb2dpbi1ncmlkIi8+CiAgICAgPC93aWRnZXRzPgogICA8L29iamVjdD4KZGlmZiAtLWdpdCBhL3BhbmVscy91c2VyLWFjY291bnRzL3VtLXVzZXItcGFuZWwuYyBiL3BhbmVscy91c2VyLWFjY291bnRzL3VtLXVzZXItcGFuZWwuYwppbmRleCBiM2E0MGJmLi5mMWI5MzYxIDEwMDY0NAotLS0gYS9wYW5lbHMvdXNlci1hY2NvdW50cy91bS11c2VyLXBhbmVsLmMKKysrIGIvcGFuZWxzL3VzZXItYWNjb3VudHMvdW0tdXNlci1wYW5lbC5jCkBAIC03OTMsMTQgKzc5MywxNCBAQCBnZXRfcGFzc3dvcmRfbW9kZV90ZXh0IChBY3RVc2VyICp1c2VyKQogfQogCiBzdGF0aWMgdm9pZAotYXV0b2xvZ2luX2NoYW5nZWQgKEdPYmplY3QgICAgICAgICAgICAqb2JqZWN0LAotICAgICAgICAgICAgICAgICAgIEdQYXJhbVNwZWMgICAgICAgICAqcHNwZWMsCi0gICAgICAgICAgICAgICAgICAgQ2NVc2VyUGFuZWxQcml2YXRlICpkKQorcGFzc3dvcmRfbG9naW5fY2hhbmdlZCAoR09iamVjdCAgICAgICAgICAgICpvYmplY3QsCisgICAgICAgICAgICAgICAgICAgICAgICBHUGFyYW1TcGVjICAgICAgICAgKnBzcGVjLAorICAgICAgICAgICAgICAgICAgICAgICAgQ2NVc2VyUGFuZWxQcml2YXRlICpkKQogewogICAgICAgICBnYm9vbGVhbiBhY3RpdmU7CiAgICAgICAgIEFjdFVzZXIgKnVzZXI7CiAKLSAgICAgICAgYWN0aXZlID0gZ3RrX3N3aXRjaF9nZXRfYWN0aXZlIChHVEtfU1dJVENIIChvYmplY3QpKTsKKyAgICAgICAgYWN0aXZlID0gIWd0a19zd2l0Y2hfZ2V0X2FjdGl2ZSAoR1RLX1NXSVRDSCAob2JqZWN0KSk7CiAgICAgICAgIHVzZXIgPSBnZXRfc2VsZWN0ZWRfdXNlciAoZCk7CiAKICAgICAgICAgaWYgKGFjdGl2ZSAhPSBhY3RfdXNlcl9nZXRfYXV0b21hdGljX2xvZ2luICh1c2VyKSkgewpAQCAtOTA2LDEwICs5MDYsMTAgQEAgc2hvd191c2VyIChBY3RVc2VyICp1c2VyLCBDY1VzZXJQYW5lbFByaXZhdGUgKmQpCiAgICAgICAgIGVuYWJsZSA9IGFjdF91c2VyX2lzX2xvY2FsX2FjY291bnQgKHVzZXIpOwogICAgICAgICBndGtfd2lkZ2V0X3NldF9zZW5zaXRpdmUgKHdpZGdldCwgZW5hYmxlKTsKIAotICAgICAgICB3aWRnZXQgPSBnZXRfd2lkZ2V0IChkLCAiYXV0b2xvZ2luLXN3aXRjaCIpOwotICAgICAgICBnX3NpZ25hbF9oYW5kbGVyc19ibG9ja19ieV9mdW5jICh3aWRnZXQsIGF1dG9sb2dpbl9jaGFuZ2VkLCBkKTsKLSAgICAgICAgZ3RrX3N3aXRjaF9zZXRfYWN0aXZlIChHVEtfU1dJVENIICh3aWRnZXQpLCBhY3RfdXNlcl9nZXRfYXV0b21hdGljX2xvZ2luICh1c2VyKSk7Ci0gICAgICAgIGdfc2lnbmFsX2hhbmRsZXJzX3VuYmxvY2tfYnlfZnVuYyAod2lkZ2V0LCBhdXRvbG9naW5fY2hhbmdlZCwgZCk7CisgICAgICAgIHdpZGdldCA9IGdldF93aWRnZXQgKGQsICJwYXNzd29yZC1sb2dpbi1zd2l0Y2giKTsKKyAgICAgICAgZ19zaWduYWxfaGFuZGxlcnNfYmxvY2tfYnlfZnVuYyAod2lkZ2V0LCBwYXNzd29yZF9sb2dpbl9jaGFuZ2VkLCBkKTsKKyAgICAgICAgZ3RrX3N3aXRjaF9zZXRfYWN0aXZlIChHVEtfU1dJVENIICh3aWRnZXQpLCAhYWN0X3VzZXJfZ2V0X2F1dG9tYXRpY19sb2dpbiAodXNlcikpOworICAgICAgICBnX3NpZ25hbF9oYW5kbGVyc191bmJsb2NrX2J5X2Z1bmMgKHdpZGdldCwgcGFzc3dvcmRfbG9naW5fY2hhbmdlZCwgZCk7CiAgICAgICAgIGd0a193aWRnZXRfc2V0X3NlbnNpdGl2ZSAod2lkZ2V0LCBnZXRfYXV0b2xvZ2luX3Bvc3NpYmxlICh1c2VyKSk7CiAKICAgICAgICAgd2lkZ2V0ID0gZ2V0X3dpZGdldCAoZCwgImFjY291bnQtbGFuZ3VhZ2UtYnV0dG9uIik7CkBAIC05MzksOSArOTM5LDkgQEAgc2hvd191c2VyIChBY3RVc2VyICp1c2VyLCBDY1VzZXJQYW5lbFByaXZhdGUgKmQpCiAgICAgICAgIGd0a193aWRnZXRfc2V0X3Zpc2libGUgKGxhYmVsLCBzaG93KTsKICAgICAgICAgZ3RrX3dpZGdldF9zZXRfdmlzaWJsZSAod2lkZ2V0LCBzaG93KTsKIAotICAgICAgICAvKiBBdXRvbG9naW46IHNob3cgd2hlbiBsb2NhbCBhY2NvdW50ICovCi0gICAgICAgIHdpZGdldCA9IGdldF93aWRnZXQgKGQsICJhdXRvbG9naW4tYm94Iik7Ci0gICAgICAgIGxhYmVsID0gZ2V0X3dpZGdldCAoZCwgImF1dG9sb2dpbi1sYWJlbCIpOworICAgICAgICAvKiBQYXNzd29yZCBsb2dpbjogc2hvdyB3aGVuIGxvY2FsIGFjY291bnQgKi8KKyAgICAgICAgd2lkZ2V0ID0gZ2V0X3dpZGdldCAoZCwgInBhc3N3b3JkLWxvZ2luLWJveCIpOworICAgICAgICBsYWJlbCA9IGdldF93aWRnZXQgKGQsICJwYXNzd29yZC1sb2dpbi1sYWJlbCIpOwogICAgICAgICBzaG93ID0gYWN0X3VzZXJfaXNfbG9jYWxfYWNjb3VudCAodXNlcik7CiAgICAgICAgIGd0a193aWRnZXRfc2V0X3Zpc2libGUgKHdpZGdldCwgc2hvdyk7CiAgICAgICAgIGd0a193aWRnZXRfc2V0X3Zpc2libGUgKGxhYmVsLCBzaG93KTsKQEAgLTEzOTYsOCArMTM5Niw4IEBAIG9uX3Blcm1pc3Npb25fY2hhbmdlZCAoR1Blcm1pc3Npb24gKnBlcm1pc3Npb24sCiAgICAgICAgIGlmICghYWN0X3VzZXJfaXNfbG9jYWxfYWNjb3VudCAodXNlcikpIHsKICAgICAgICAgICAgICAgICBndGtfc3RhY2tfc2V0X3Zpc2libGVfY2hpbGRfbmFtZSAoR1RLX1NUQUNLIChnZXRfd2lkZ2V0IChkLCAiYWNjb3VudC10eXBlLXN0YWNrIikpLCAic3RhdGljIik7CiAgICAgICAgICAgICAgICAgcmVtb3ZlX3VubG9ja190b29sdGlwIChnZXRfd2lkZ2V0IChkLCAiYWNjb3VudC10eXBlLXN0YWNrIikpOwotICAgICAgICAgICAgICAgIGd0a193aWRnZXRfc2V0X3NlbnNpdGl2ZSAoR1RLX1dJREdFVCAoZ2V0X3dpZGdldCAoZCwgImF1dG9sb2dpbi1zd2l0Y2giKSksIEZBTFNFKTsKLSAgICAgICAgICAgICAgICByZW1vdmVfdW5sb2NrX3Rvb2x0aXAgKGdldF93aWRnZXQgKGQsICJhdXRvbG9naW4tc3dpdGNoIikpOworICAgICAgICAgICAgICAgIGd0a193aWRnZXRfc2V0X3NlbnNpdGl2ZSAoR1RLX1dJREdFVCAoZ2V0X3dpZGdldCAoZCwgInBhc3N3b3JkLWxvZ2luLXN3aXRjaCIpKSwgRkFMU0UpOworICAgICAgICAgICAgICAgIHJlbW92ZV91bmxvY2tfdG9vbHRpcCAoZ2V0X3dpZGdldCAoZCwgInBhc3N3b3JkLWxvZ2luLXN3aXRjaCIpKTsKIAogICAgICAgICB9IGVsc2UgaWYgKGlzX2F1dGhvcml6ZWQgJiYgYWN0X3VzZXJfaXNfbG9jYWxfYWNjb3VudCAodXNlcikpIHsKICAgICAgICAgICAgICAgICBpZiAod291bGRfZGVtb3RlX29ubHlfYWRtaW4gKHVzZXIpKSB7CkBAIC0xNDA3LDggKzE0MDcsOCBAQCBvbl9wZXJtaXNzaW9uX2NoYW5nZWQgKEdQZXJtaXNzaW9uICpwZXJtaXNzaW9uLAogICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICByZW1vdmVfdW5sb2NrX3Rvb2x0aXAgKGdldF93aWRnZXQgKGQsICJhY2NvdW50LXR5cGUtc3RhY2siKSk7CiAKLSAgICAgICAgICAgICAgICBndGtfd2lkZ2V0X3NldF9zZW5zaXRpdmUgKEdUS19XSURHRVQgKGdldF93aWRnZXQgKGQsICJhdXRvbG9naW4tc3dpdGNoIikpLCBnZXRfYXV0b2xvZ2luX3Bvc3NpYmxlICh1c2VyKSk7Ci0gICAgICAgICAgICAgICAgcmVtb3ZlX3VubG9ja190b29sdGlwIChnZXRfd2lkZ2V0IChkLCAiYXV0b2xvZ2luLXN3aXRjaCIpKTsKKyAgICAgICAgICAgICAgICBndGtfd2lkZ2V0X3NldF9zZW5zaXRpdmUgKEdUS19XSURHRVQgKGdldF93aWRnZXQgKGQsICJwYXNzd29yZC1sb2dpbi1zd2l0Y2giKSksIGdldF9hdXRvbG9naW5fcG9zc2libGUgKHVzZXIpKTsKKyAgICAgICAgICAgICAgICByZW1vdmVfdW5sb2NrX3Rvb2x0aXAgKGdldF93aWRnZXQgKGQsICJwYXNzd29yZC1sb2dpbi1zd2l0Y2giKSk7CiAgICAgICAgIH0KICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICAgZ3RrX3N0YWNrX3NldF92aXNpYmxlX2NoaWxkX25hbWUgKEdUS19TVEFDSyAoZ2V0X3dpZGdldCAoZCwgImFjY291bnQtdHlwZS1zdGFjayIpKSwgInN0YXRpYyIpOwpAQCAtMTQxNyw4ICsxNDE3LDggQEAgb25fcGVybWlzc2lvbl9jaGFuZ2VkIChHUGVybWlzc2lvbiAqcGVybWlzc2lvbiwKICAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICAgYWRkX3VubG9ja190b29sdGlwIChnZXRfd2lkZ2V0IChkLCAiYWNjb3VudC10eXBlLXN0YWNrIikpOwogICAgICAgICAgICAgICAgIH0KLSAgICAgICAgICAgICAgICBndGtfd2lkZ2V0X3NldF9zZW5zaXRpdmUgKEdUS19XSURHRVQgKGdldF93aWRnZXQgKGQsICJhdXRvbG9naW4tc3dpdGNoIikpLCBGQUxTRSk7Ci0gICAgICAgICAgICAgICAgYWRkX3VubG9ja190b29sdGlwIChnZXRfd2lkZ2V0IChkLCAiYXV0b2xvZ2luLXN3aXRjaCIpKTsKKyAgICAgICAgICAgICAgICBndGtfd2lkZ2V0X3NldF9zZW5zaXRpdmUgKEdUS19XSURHRVQgKGdldF93aWRnZXQgKGQsICJwYXNzd29yZC1sb2dpbi1zd2l0Y2giKSksIEZBTFNFKTsKKyAgICAgICAgICAgICAgICBhZGRfdW5sb2NrX3Rvb2x0aXAgKGdldF93aWRnZXQgKGQsICJwYXNzd29yZC1sb2dpbi1zd2l0Y2giKSk7CiAgICAgICAgIH0KIAogICAgICAgICAvKiBUaGUgZnVsbCBuYW1lIGVudHJ5OiBpbnNlbnNpdGl2ZSBpZiByZW1vdGUgb3Igbm90IGF1dGhvcml6ZWQgYW5kIG5vdCBzZWxmICovCkBAIC0xNjQ5LDggKzE2NDksOCBAQCBzZXR1cF9tYWluX3dpbmRvdyAoQ2NVc2VyUGFuZWwgKnNlbGYpCiAgICAgICAgIGJ1dHRvbiA9IGdldF93aWRnZXQgKGQsICJhY2NvdW50LWxhbmd1YWdlLWJ1dHRvbiIpOwogICAgICAgICBnX3NpZ25hbF9jb25uZWN0IChidXR0b24sICJzdGFydC1lZGl0aW5nIiwgR19DQUxMQkFDSyAoY2hhbmdlX2xhbmd1YWdlKSwgZCk7CiAKLSAgICAgICAgYnV0dG9uID0gZ2V0X3dpZGdldCAoZCwgImF1dG9sb2dpbi1zd2l0Y2giKTsKLSAgICAgICAgZ19zaWduYWxfY29ubmVjdCAoYnV0dG9uLCAibm90aWZ5OjphY3RpdmUiLCBHX0NBTExCQUNLIChhdXRvbG9naW5fY2hhbmdlZCksIGQpOworICAgICAgICBidXR0b24gPSBnZXRfd2lkZ2V0IChkLCAicGFzc3dvcmQtbG9naW4tc3dpdGNoIik7CisgICAgICAgIGdfc2lnbmFsX2Nvbm5lY3QgKGJ1dHRvbiwgIm5vdGlmeTo6YWN0aXZlIiwgR19DQUxMQkFDSyAocGFzc3dvcmRfbG9naW5fY2hhbmdlZCksIGQpOwogCiAgICAgICAgIGJ1dHRvbiA9IGdldF93aWRnZXQgKGQsICJhY2NvdW50LWZpbmdlcnByaW50LWJ1dHRvbiIpOwogICAgICAgICBnX3NpZ25hbF9jb25uZWN0IChidXR0b24sICJzdGFydC1lZGl0aW5nIiwKLS0gCjIuNy40bugs 116 | http_version: 117 | recorded_at: Tue, 23 Oct 2018 10:50:52 GMT 118 | recorded_with: VCR 4.0.0 119 | -------------------------------------------------------------------------------- /test/fixtures/vcr/bugzilla_gnome_org_777777.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 6 | body: 7 | encoding: UTF-8 8 | string: 'Bug.getids777777 9 | 10 | ' 11 | headers: 12 | User-Agent: 13 | - XMLRPC::Client (Ruby 2.5.0) 14 | Content-Type: 15 | - text/xml; charset=utf-8 16 | Content-Length: 17 | - '250' 18 | Connection: 19 | - keep-alive 20 | Accept-Encoding: 21 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 22 | Accept: 23 | - "*/*" 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Date: 30 | - Tue, 23 Oct 2018 10:53:58 GMT 31 | Server: 32 | - Apache/2.4.6 (Red Hat Enterprise Linux) 33 | X-Xss-Protection: 34 | - 1; mode=block 35 | X-Frame-Options: 36 | - SAMEORIGIN 37 | X-Content-Type-Options: 38 | - nosniff 39 | Set-Cookie: 40 | - Bugzilla_login_request_cookie=5IiMrJoslx; path=/; secure; HttpOnly 41 | Content-Length: 42 | - '3085' 43 | Content-Type: 44 | - text/xml 45 | Soapserver: 46 | - SOAP::Lite/Perl/1.1 47 | Strict-Transport-Security: 48 | - max-age=31536000; includeSubDomains; preload 49 | Access-Control-Allow-Origin: 50 | - https://bugzilla.gnome.org 51 | Connection: 52 | - close 53 | body: 54 | encoding: UTF-8 55 | string: faultsbugspriorityNormalblockscreatorkekun.plazas@laposte.netlast_change_time20170222T20:48:43is_cc_accessible1keywordscckekun.plazas@laposte.netmcatanzaro@gnome.orgurlassigned_tognome-games-maint@gnome.bugsgroupssee_alsoid777777creation_time20170126T08:46:00whiteboardqa_contactgnome-games-maint@gnome.bugsdepends_onresolutionFIXEDclassificationCoreop_sysLinuxstatusRESOLVEDcf_gnome_target---cf_gnome_version---summaryGame 64 | Boy games not detectedis_open0platformOtherseveritynormalflagsversionunspecifiedcomponentgeneralis_creator_accessible1productgnome-gamesis_confirmed1target_milestone--- 66 | http_version: 67 | recorded_at: Tue, 23 Oct 2018 10:53:59 GMT 68 | - request: 69 | method: post 70 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 71 | body: 72 | encoding: UTF-8 73 | string: 'Bug.historyids777777 74 | 75 | ' 76 | headers: 77 | User-Agent: 78 | - XMLRPC::Client (Ruby 2.5.0) 79 | Content-Type: 80 | - text/xml; charset=utf-8 81 | Content-Length: 82 | - '254' 83 | Connection: 84 | - keep-alive 85 | Cookie: 86 | - Bugzilla_login_request_cookie=5IiMrJoslx 87 | Accept-Encoding: 88 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 89 | Accept: 90 | - "*/*" 91 | response: 92 | status: 93 | code: 200 94 | message: OK 95 | headers: 96 | Date: 97 | - Tue, 23 Oct 2018 10:53:58 GMT 98 | Server: 99 | - Apache/2.4.6 (Red Hat Enterprise Linux) 100 | X-Xss-Protection: 101 | - 1; mode=block 102 | X-Frame-Options: 103 | - SAMEORIGIN 104 | X-Content-Type-Options: 105 | - nosniff 106 | Content-Length: 107 | - '4959' 108 | Content-Type: 109 | - text/xml 110 | Soapserver: 111 | - SOAP::Lite/Perl/1.1 112 | Strict-Transport-Security: 113 | - max-age=31536000; includeSubDomains; preload 114 | Access-Control-Allow-Origin: 115 | - https://bugzilla.gnome.org 116 | Connection: 117 | - close 118 | body: 119 | encoding: UTF-8 120 | string: bugshistorywhen20170126T09:03:10changesremovedaddedkekun.plazas@laposte.netfield_nameccwhokekun.plazas@laposte.netwhen20170126T09:05:34changesattachment_id344292removed0added1field_nameattachments.isobsoletewhokekun.plazas@laposte.netwhen20170126T09:06:09changesremovedNEWaddedRESOLVEDfield_namestatusremovedaddedFIXEDfield_nameresolutionwhokekun.plazas@laposte.netwhen20170126T09:06:13changesattachment_id344293removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T09:06:16changesattachment_id344294removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T11:06:35changesattachment_id344303removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170222T20:40:19changesremovedaddedmcatanzaro@gnome.orgfield_nameccwhomcatanzaro@gnome.orgid777777 124 | http_version: 125 | recorded_at: Tue, 23 Oct 2018 10:54:00 GMT 126 | recorded_with: VCR 4.0.0 127 | -------------------------------------------------------------------------------- /test/fixtures/vcr/bugzilla_gnome_org_777777_export.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 6 | body: 7 | encoding: UTF-8 8 | string: 'Bug.getids777777 9 | 10 | ' 11 | headers: 12 | User-Agent: 13 | - XMLRPC::Client (Ruby 2.5.0) 14 | Content-Type: 15 | - text/xml; charset=utf-8 16 | Content-Length: 17 | - '250' 18 | Connection: 19 | - keep-alive 20 | Accept-Encoding: 21 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 22 | Accept: 23 | - "*/*" 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Date: 30 | - Tue, 23 Oct 2018 11:53:52 GMT 31 | Server: 32 | - Apache/2.4.6 (Red Hat Enterprise Linux) 33 | X-Xss-Protection: 34 | - 1; mode=block 35 | X-Frame-Options: 36 | - SAMEORIGIN 37 | X-Content-Type-Options: 38 | - nosniff 39 | Set-Cookie: 40 | - Bugzilla_login_request_cookie=G3hZl9A9vP; path=/; secure; HttpOnly 41 | Content-Length: 42 | - '3085' 43 | Content-Type: 44 | - text/xml 45 | Soapserver: 46 | - SOAP::Lite/Perl/1.1 47 | Strict-Transport-Security: 48 | - max-age=31536000; includeSubDomains; preload 49 | Access-Control-Allow-Origin: 50 | - https://bugzilla.gnome.org 51 | Connection: 52 | - close 53 | body: 54 | encoding: UTF-8 55 | string: faultsbugspriorityNormalblockscreatorkekun.plazas@laposte.netlast_change_time20170222T20:48:43is_cc_accessible1keywordscckekun.plazas@laposte.netmcatanzaro@gnome.orgurlassigned_tognome-games-maint@gnome.bugsgroupssee_alsoid777777creation_time20170126T08:46:00whiteboardqa_contactgnome-games-maint@gnome.bugsdepends_onresolutionFIXEDclassificationCoreop_sysLinuxstatusRESOLVEDcf_gnome_target---cf_gnome_version---summaryGame 64 | Boy games not detectedis_open0platformOtherseveritynormalflagsversionunspecifiedcomponentgeneralis_creator_accessible1productgnome-gamesis_confirmed1target_milestone--- 66 | http_version: 67 | recorded_at: Tue, 23 Oct 2018 11:53:54 GMT 68 | - request: 69 | method: post 70 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 71 | body: 72 | encoding: UTF-8 73 | string: 'Bug.historyids777777 74 | 75 | ' 76 | headers: 77 | User-Agent: 78 | - XMLRPC::Client (Ruby 2.5.0) 79 | Content-Type: 80 | - text/xml; charset=utf-8 81 | Content-Length: 82 | - '254' 83 | Connection: 84 | - keep-alive 85 | Cookie: 86 | - Bugzilla_login_request_cookie=G3hZl9A9vP 87 | Accept-Encoding: 88 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 89 | Accept: 90 | - "*/*" 91 | response: 92 | status: 93 | code: 200 94 | message: OK 95 | headers: 96 | Date: 97 | - Tue, 23 Oct 2018 11:53:53 GMT 98 | Server: 99 | - Apache/2.4.6 (Red Hat Enterprise Linux) 100 | X-Xss-Protection: 101 | - 1; mode=block 102 | X-Frame-Options: 103 | - SAMEORIGIN 104 | X-Content-Type-Options: 105 | - nosniff 106 | Content-Length: 107 | - '4959' 108 | Content-Type: 109 | - text/xml 110 | Soapserver: 111 | - SOAP::Lite/Perl/1.1 112 | Strict-Transport-Security: 113 | - max-age=31536000; includeSubDomains; preload 114 | Access-Control-Allow-Origin: 115 | - https://bugzilla.gnome.org 116 | Connection: 117 | - close 118 | body: 119 | encoding: UTF-8 120 | string: bugshistorywhen20170126T09:03:10changesremovedaddedkekun.plazas@laposte.netfield_nameccwhokekun.plazas@laposte.netwhen20170126T09:05:34changesattachment_id344292removed0added1field_nameattachments.isobsoletewhokekun.plazas@laposte.netwhen20170126T09:06:09changesremovedNEWaddedRESOLVEDfield_namestatusremovedaddedFIXEDfield_nameresolutionwhokekun.plazas@laposte.netwhen20170126T09:06:13changesattachment_id344293removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T09:06:16changesattachment_id344294removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T11:06:35changesattachment_id344303removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170222T20:40:19changesremovedaddedmcatanzaro@gnome.orgfield_nameccwhomcatanzaro@gnome.orgid777777 124 | http_version: 125 | recorded_at: Tue, 23 Oct 2018 11:53:54 GMT 126 | - request: 127 | method: post 128 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 129 | body: 130 | encoding: UTF-8 131 | string: 'Bug.historyids777777 132 | 133 | ' 134 | headers: 135 | User-Agent: 136 | - XMLRPC::Client (Ruby 2.5.0) 137 | Content-Type: 138 | - text/xml; charset=utf-8 139 | Content-Length: 140 | - '254' 141 | Connection: 142 | - keep-alive 143 | Cookie: 144 | - Bugzilla_login_request_cookie=G3hZl9A9vP 145 | Accept-Encoding: 146 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 147 | Accept: 148 | - "*/*" 149 | response: 150 | status: 151 | code: 200 152 | message: OK 153 | headers: 154 | Date: 155 | - Tue, 23 Oct 2018 11:53:54 GMT 156 | Server: 157 | - Apache/2.4.6 (Red Hat Enterprise Linux) 158 | X-Xss-Protection: 159 | - 1; mode=block 160 | X-Frame-Options: 161 | - SAMEORIGIN 162 | X-Content-Type-Options: 163 | - nosniff 164 | Content-Length: 165 | - '4959' 166 | Content-Type: 167 | - text/xml 168 | Soapserver: 169 | - SOAP::Lite/Perl/1.1 170 | Strict-Transport-Security: 171 | - max-age=31536000; includeSubDomains; preload 172 | Access-Control-Allow-Origin: 173 | - https://bugzilla.gnome.org 174 | Connection: 175 | - close 176 | body: 177 | encoding: UTF-8 178 | string: bugshistorywhen20170126T09:03:10changesremovedaddedkekun.plazas@laposte.netfield_nameccwhokekun.plazas@laposte.netwhen20170126T09:05:34changesattachment_id344292removed0added1field_nameattachments.isobsoletewhokekun.plazas@laposte.netwhen20170126T09:06:09changesremovedNEWaddedRESOLVEDfield_namestatusremovedaddedFIXEDfield_nameresolutionwhokekun.plazas@laposte.netwhen20170126T09:06:13changesattachment_id344293removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T09:06:16changesattachment_id344294removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T11:06:35changesattachment_id344303removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170222T20:40:19changesremovedaddedmcatanzaro@gnome.orgfield_nameccwhomcatanzaro@gnome.orgid777777 182 | http_version: 183 | recorded_at: Tue, 23 Oct 2018 11:53:55 GMT 184 | - request: 185 | method: post 186 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 187 | body: 188 | encoding: UTF-8 189 | string: 'Bug.historyids777777 190 | 191 | ' 192 | headers: 193 | User-Agent: 194 | - XMLRPC::Client (Ruby 2.5.0) 195 | Content-Type: 196 | - text/xml; charset=utf-8 197 | Content-Length: 198 | - '254' 199 | Connection: 200 | - keep-alive 201 | Cookie: 202 | - Bugzilla_login_request_cookie=G3hZl9A9vP 203 | Accept-Encoding: 204 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 205 | Accept: 206 | - "*/*" 207 | response: 208 | status: 209 | code: 200 210 | message: OK 211 | headers: 212 | Date: 213 | - Tue, 23 Oct 2018 11:53:54 GMT 214 | Server: 215 | - Apache/2.4.6 (Red Hat Enterprise Linux) 216 | X-Xss-Protection: 217 | - 1; mode=block 218 | X-Frame-Options: 219 | - SAMEORIGIN 220 | X-Content-Type-Options: 221 | - nosniff 222 | Content-Length: 223 | - '4959' 224 | Content-Type: 225 | - text/xml 226 | Soapserver: 227 | - SOAP::Lite/Perl/1.1 228 | Strict-Transport-Security: 229 | - max-age=31536000; includeSubDomains; preload 230 | Access-Control-Allow-Origin: 231 | - https://bugzilla.gnome.org 232 | Connection: 233 | - close 234 | body: 235 | encoding: UTF-8 236 | string: bugshistorywhen20170126T09:03:10changesremovedaddedkekun.plazas@laposte.netfield_nameccwhokekun.plazas@laposte.netwhen20170126T09:05:34changesattachment_id344292removed0added1field_nameattachments.isobsoletewhokekun.plazas@laposte.netwhen20170126T09:06:09changesremovedNEWaddedRESOLVEDfield_namestatusremovedaddedFIXEDfield_nameresolutionwhokekun.plazas@laposte.netwhen20170126T09:06:13changesattachment_id344293removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T09:06:16changesattachment_id344294removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T11:06:35changesattachment_id344303removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170222T20:40:19changesremovedaddedmcatanzaro@gnome.orgfield_nameccwhomcatanzaro@gnome.orgid777777 240 | http_version: 241 | recorded_at: Tue, 23 Oct 2018 11:53:55 GMT 242 | - request: 243 | method: post 244 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 245 | body: 246 | encoding: UTF-8 247 | string: 'Bug.historyids777777 248 | 249 | ' 250 | headers: 251 | User-Agent: 252 | - XMLRPC::Client (Ruby 2.5.0) 253 | Content-Type: 254 | - text/xml; charset=utf-8 255 | Content-Length: 256 | - '254' 257 | Connection: 258 | - keep-alive 259 | Cookie: 260 | - Bugzilla_login_request_cookie=G3hZl9A9vP 261 | Accept-Encoding: 262 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 263 | Accept: 264 | - "*/*" 265 | response: 266 | status: 267 | code: 200 268 | message: OK 269 | headers: 270 | Date: 271 | - Tue, 23 Oct 2018 11:53:55 GMT 272 | Server: 273 | - Apache/2.4.6 (Red Hat Enterprise Linux) 274 | X-Xss-Protection: 275 | - 1; mode=block 276 | X-Frame-Options: 277 | - SAMEORIGIN 278 | X-Content-Type-Options: 279 | - nosniff 280 | Content-Length: 281 | - '4959' 282 | Content-Type: 283 | - text/xml 284 | Soapserver: 285 | - SOAP::Lite/Perl/1.1 286 | Strict-Transport-Security: 287 | - max-age=31536000; includeSubDomains; preload 288 | Access-Control-Allow-Origin: 289 | - https://bugzilla.gnome.org 290 | Connection: 291 | - close 292 | body: 293 | encoding: UTF-8 294 | string: bugshistorywhen20170126T09:03:10changesremovedaddedkekun.plazas@laposte.netfield_nameccwhokekun.plazas@laposte.netwhen20170126T09:05:34changesattachment_id344292removed0added1field_nameattachments.isobsoletewhokekun.plazas@laposte.netwhen20170126T09:06:09changesremovedNEWaddedRESOLVEDfield_namestatusremovedaddedFIXEDfield_nameresolutionwhokekun.plazas@laposte.netwhen20170126T09:06:13changesattachment_id344293removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T09:06:16changesattachment_id344294removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170126T11:06:35changesattachment_id344303removednoneaddedcommittedfield_nameattachments.gnome_attachment_statuswhokekun.plazas@laposte.netwhen20170222T20:40:19changesremovedaddedmcatanzaro@gnome.orgfield_nameccwhomcatanzaro@gnome.orgid777777 298 | http_version: 299 | recorded_at: Tue, 23 Oct 2018 11:53:56 GMT 300 | recorded_with: VCR 4.0.0 301 | -------------------------------------------------------------------------------- /test/fixtures/vcr/bugzilla_gnome_org_version.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://bugzilla.gnome.org/xmlrpc.cgi 6 | body: 7 | encoding: UTF-8 8 | string: 'Bugzilla.version 9 | 10 | ' 11 | headers: 12 | User-Agent: 13 | - XMLRPC::Client (Ruby 2.5.0) 14 | Content-Type: 15 | - text/xml; charset=utf-8 16 | Content-Length: 17 | - '98' 18 | Connection: 19 | - keep-alive 20 | Accept-Encoding: 21 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 22 | Accept: 23 | - "*/*" 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Date: 30 | - Tue, 23 Oct 2018 10:54:56 GMT 31 | Server: 32 | - Apache/2.4.6 (Red Hat Enterprise Linux) 33 | X-Xss-Protection: 34 | - 1; mode=block 35 | X-Frame-Options: 36 | - SAMEORIGIN 37 | X-Content-Type-Options: 38 | - nosniff 39 | Set-Cookie: 40 | - Bugzilla_login_request_cookie=rGRBUqdGxW; path=/; secure; HttpOnly 41 | Content-Length: 42 | - '210' 43 | Content-Type: 44 | - text/xml 45 | Soapserver: 46 | - SOAP::Lite/Perl/1.1 47 | Strict-Transport-Security: 48 | - max-age=31536000; includeSubDomains; preload 49 | Access-Control-Allow-Origin: 50 | - https://bugzilla.gnome.org 51 | Connection: 52 | - close 53 | body: 54 | encoding: UTF-8 55 | string: version4.4.13 56 | http_version: 57 | recorded_at: Tue, 23 Oct 2018 10:54:57 GMT 58 | recorded_with: VCR 4.0.0 59 | -------------------------------------------------------------------------------- /test/fixtures/vcr/bugzilla_opensuse_org_version.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://bugzilla.opensuse.org/xmlrpc.cgi 6 | body: 7 | encoding: UTF-8 8 | string: 'Bugzilla.version 9 | 10 | ' 11 | headers: 12 | User-Agent: 13 | - XMLRPC::Client (Ruby 2.5.0) 14 | Content-Type: 15 | - text/xml; charset=utf-8 16 | Content-Length: 17 | - '98' 18 | Connection: 19 | - keep-alive 20 | Accept-Encoding: 21 | - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 22 | Accept: 23 | - "*/*" 24 | response: 25 | status: 26 | code: 200 27 | message: OK 28 | headers: 29 | Date: 30 | - Tue, 23 Oct 2018 10:54:58 GMT 31 | Server: 32 | - Apache 33 | Strict-Transport-Security: 34 | - max-age=31536000;includeSubDomains 35 | X-Content-Type-Options: 36 | - nosniff 37 | X-Frame-Options: 38 | - SAMEORIGIN 39 | X-Xss-Protection: 40 | - 1; mode=block 41 | Content-Type: 42 | - text/xml 43 | Soapserver: 44 | - SOAP::Lite/Perl/1.11 45 | Set-Cookie: 46 | - Bugzilla_login_request_cookie=jTyWtaThES; path=/; HttpOnly 47 | - TbBx5iTmnWSKRFA@=v18jFvAA@@6d3; Domain=.opensuse.org; Path=/ 48 | - ZNPCQ003-32343300=1ea19fc3; Path=/; Domain=.opensuse.org 49 | X-Mag: 50 | - A816300EE4BD0ACF;5faca923;2440571;usrLkup->0;usrBase->0;getPRBefFind->0;getPRBefFind->0;PRAfterFind->0;openbugzilla_root;publicURL->0;openbugzilla;RwDis;FF1Endp->0;SendSoapStart->0;SendSoapExit->2;EvalII->2;CHd;FP2->2;WS=1ea19fc3;FP4->21; 51 | Via: 52 | - 1.1 bugzilla.opensuse.org (Access Gateway-ag-A816300EE4BD0ACF-2440571) 53 | Keep-Alive: 54 | - timeout=2, max=100 55 | Connection: 56 | - Keep-Alive 57 | Transfer-Encoding: 58 | - chunked 59 | Vary: 60 | - Accept-encoding 61 | body: 62 | encoding: ASCII-8BIT 63 | string: version4.4.12 64 | http_version: 65 | recorded_at: Tue, 23 Oct 2018 10:54:58 GMT 66 | recorded_with: VCR 4.0.0 67 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib') 4 | require 'bundler/setup' 5 | require 'minitest/autorun' 6 | require 'minitest/reporters' 7 | require 'minitest/mock' 8 | require 'vcr' 9 | 10 | Minitest::Reporters.use!( 11 | Minitest::Reporters::ProgressReporter.new, 12 | ENV, 13 | Minitest.backtrace_filter 14 | ) 15 | require 'bicho' 16 | 17 | if ENV['DEBUG'] 18 | Bicho::Logging.logger = Logger.new(STDERR) 19 | Bicho::Logging.logger.level = Logger::DEBUG 20 | end 21 | 22 | def fixture(path) 23 | File.absolute_path(File.join(File.dirname(__FILE__), '..', 'test', 'fixtures', path)) 24 | end 25 | 26 | VCR.configure do |config| 27 | config.cassette_library_dir = fixture('vcr') 28 | config.hook_into :webmock 29 | end 30 | -------------------------------------------------------------------------------- /test/test_attachments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | require 'digest' 5 | 6 | # Test for bug attachments 7 | class AttachmentsTest < Minitest::Test 8 | def test_client_get_attachments 9 | VCR.use_cassette('bugzilla.gnome.org_679745_attachments') do 10 | Bicho.client = Bicho::Client.new('https://bugzilla.gnome.org') 11 | 12 | attachments = Bicho.client.get_attachments(679745) 13 | assert_kind_of(Array, attachments) 14 | assert !attachments.empty? 15 | 16 | attachments.each do |attachment| 17 | assert_kind_of(Bicho::Attachment, attachment) 18 | 19 | next unless attachment.id == 329690 20 | assert_equal('text/plain', attachment.content_type) 21 | assert_equal(8559, attachment.size) 22 | assert_equal('user-accounts: use Password Login instead of Automatic Login', 23 | attachment.summary) 24 | 25 | assert_equal('80c4665205bcbd2b90ea920eff21f29988d8fc85f36c293a65fa3dad52d19354', 26 | Digest::SHA256.hexdigest(attachment.data.read)) 27 | end 28 | end 29 | end 30 | 31 | def teardown 32 | Bicho.client = nil 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/test_export.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Test small utilities 6 | class ExportTest < Minitest::Test 7 | def test_to_json 8 | VCR.use_cassette('bugzilla.gnome.org_777777_export') do 9 | Bicho.client = Bicho::Client.new('https://bugzilla.gnome.org') 10 | bug = Bicho.client.get_bug(777777) 11 | File.write('777777.json', Bicho::Export.to_json(bug)) 12 | 13 | assert_equal( 14 | JSON.dump(JSON.load(File.read(fixture('777777.json')))), 15 | Bicho::Export.to_json(bug) 16 | ) 17 | end 18 | end 19 | 20 | def test_to_prometheus 21 | VCR.use_cassette('bugzilla.gnome.org_search_prometheus_export') do 22 | Bicho.client = Bicho::Client.new('https://bugzilla.gnome.org') 23 | query = Bicho::Query.new.product('vala').product('gnome-terminal').open 24 | export = Bicho::Export.to_prometheus_push_gateway(query) 25 | assert_equal( 26 | File.read(fixture('bugzilla_gnome_org_search_prometheus_export.txt')), 27 | export 28 | ) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/test_history.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Test for bug history 6 | class HistoryTest < Minitest::Test 7 | def test_basic_history 8 | VCR.use_cassette('bugzilla.gnome.org_645150_history') do 9 | Bicho.client = Bicho::Client.new('https://bugzilla.gnome.org') 10 | 11 | bug = Bicho.client.get_bug(645150) 12 | history = bug.history 13 | 14 | assert !history.empty? 15 | 16 | history.each do |c| 17 | assert c.timestamp.to_time.to_i.positive? 18 | end 19 | end 20 | end 21 | 22 | def teardown 23 | Bicho.client = nil 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/test_novell_plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | require 'bicho/plugins/novell' 5 | require 'logger' 6 | 7 | # Test for the plugin supporting the Novell/SUSE bugzilla authentication 8 | class NovellPluginTest < Minitest::Test 9 | def test_url_replacement 10 | creds = { user: 'test', password: 'test' } 11 | %w(novell suse).each do |domain| 12 | r, w = IO.pipe 13 | log = Logger.new(w) 14 | Bicho::Plugins::Novell.stub :oscrc_credentials, creds do 15 | plugin = Bicho::Plugins::Novell.new 16 | url = URI.parse("http://bugzilla.#{domain}.com") 17 | site_url = plugin.transform_site_url_hook(url, log) 18 | api_url = plugin.transform_api_url_hook(url, log) 19 | assert_equal(site_url.to_s, "http://bugzilla.#{domain}.com") 20 | assert_equal(api_url.to_s, "https://apibugzilla.#{domain}.com") 21 | assert_match(/Rewrote url/, r.gets) 22 | end 23 | end 24 | end 25 | 26 | def test_xmlrpcclient_replacement 27 | creds = { user: 'test', password: 'test' } 28 | %w(novell suse).each do |domain| 29 | Bicho::Plugins::Novell.stub :oscrc_credentials, creds do 30 | r, w = IO.pipe 31 | log = Logger.new(w) 32 | 33 | plugin = Bicho::Plugins::Novell.new 34 | url = URI.parse("http://bugzilla.#{domain}.com") 35 | api_url = plugin.transform_api_url_hook(url, log) 36 | client = XMLRPC::Client.new_from_uri(api_url.to_s, nil, 900) 37 | plugin.transform_xmlrpc_client_hook(client, log) 38 | w.close 39 | assert_equal('test', client.user, 'xmlrpc client username should be set') 40 | assert_equal('test', client.password, 'xmlrpc client password should be set') 41 | assert_match(/updated XMLRPC client with oscrc auth information/, r.read) 42 | end 43 | end 44 | end 45 | 46 | def test_oscrc_parsing 47 | oscrc = < %w(foo bar) }, ret.query_map) 30 | end 31 | 32 | def test_query_shortcuts 33 | ret = Bicho::Query.new.open 34 | assert_equal({ 'status' => [:new, :assigned, :needinfo, :reopened, :confirmed, :in_progress] }, ret.query_map) 35 | end 36 | 37 | def teardown 38 | Bicho.client = nil 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /test/test_reports.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | # Test reports on bugs 6 | class ReportsTest < Minitest::Test 7 | def test_resolution_date 8 | VCR.use_cassette('bugzilla.gnome.org_777777') do 9 | Bicho.client = Bicho::Client.new('https://bugzilla.gnome.org') 10 | bug = Bicho.client.get_bug(777777) 11 | ts = Bicho::Reports.resolution_time(bug) 12 | assert_equal(Time.parse('2017-01-26 09:06:09 UTC'), ts) 13 | end 14 | end 15 | 16 | def test_ranges_with_statuses 17 | VCR.use_cassette('bugzilla.gnome.org_312619') do 18 | Bicho.client = Bicho::Client.new('https://bugzilla.gnome.org') 19 | bug = Bicho.client.get_bug(312619) 20 | ranges = Bicho::Reports.ranges_with_statuses(bug, 'NEEDINFO') 21 | assert_equal( 22 | [Time.parse('2005-12-31 21:56:36 UTC')..Time.parse('2006-04-11 19:22:41 UTC')], ranges 23 | ) 24 | 25 | ranges = Bicho::Reports.ranges_with_statuses(bug, 'NEEDINFO', 'RESOLVED') 26 | assert_equal(Time.parse('2005-12-31 21:56:36 UTC')..Time.parse('2006-04-11 19:22:41 UTC'), ranges[0]) 27 | assert_equal(Time.parse('2006-04-11 19:22:41 UTC'), ranges[1].begin) 28 | assert(Time.now - ranges[1].end < 60) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/test_user_plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | require 'bicho/plugins/user' 5 | require 'tmpdir' 6 | 7 | # Test for the plugin implementing user 8 | # preferences 9 | class UserPluginTest < Minitest::Test 10 | def self.write_fake_config(path) 11 | File.open(path, 'w') do |f| 12 | f.write(<